From dc22d462bcb04f6e70f590dcf34f5a8223dd0b80 Mon Sep 17 00:00:00 2001 From: Andy Fingerhut Date: Fri, 14 Mar 2025 15:15:22 +0000 Subject: [PATCH 1/5] Add source code of bf_pktpy modules Signed-off-by: Andy Fingerhut --- src/bf_pktpy/__init__.py | 19 + src/bf_pktpy/__main__.py | 17 + src/bf_pktpy/all/__init__.py | 19 + src/bf_pktpy/build_information.py | 23 + src/bf_pktpy/commands.py | 408 +++++++++++ src/bf_pktpy/library/__init__.py | 0 src/bf_pktpy/library/fields/__init__.py | 32 + src/bf_pktpy/library/fields/bit_enum_field.py | 6 + src/bf_pktpy/library/fields/bit_field.py | 29 + .../library/fields/byte_enum_field.py | 6 + src/bf_pktpy/library/fields/byte_field.py | 6 + .../library/fields/conditional_field.py | 35 + src/bf_pktpy/library/fields/dest_ip_field.py | 23 + src/bf_pktpy/library/fields/dest_mac_field.py | 6 + src/bf_pktpy/library/fields/enum_field.py | 81 +++ src/bf_pktpy/library/fields/field.py | 138 ++++ src/bf_pktpy/library/fields/flag_value.py | 31 + src/bf_pktpy/library/fields/flags_field.py | 68 ++ src/bf_pktpy/library/fields/int_field.py | 6 + src/bf_pktpy/library/fields/ip_field.py | 66 ++ src/bf_pktpy/library/fields/ip_list_field.py | 47 ++ .../library/fields/ipoptions_list_field.py | 58 ++ src/bf_pktpy/library/fields/mac_field.py | 55 ++ .../library/fields/short_enum_field.py | 6 + src/bf_pktpy/library/fields/short_field.py | 6 + .../library/fields/source_ip_field.py | 41 ++ .../library/fields/source_mac_field.py | 9 + src/bf_pktpy/library/fields/str_field.py | 57 ++ .../library/fields/three_bytes_field.py | 6 + src/bf_pktpy/library/fields/x_3byte_field.py | 6 + src/bf_pktpy/library/fields/x_bit_field.py | 17 + src/bf_pktpy/library/fields/x_byte_field.py | 6 + src/bf_pktpy/library/fields/x_int_field.py | 6 + src/bf_pktpy/library/fields/x_long_field.py | 6 + .../library/fields/x_short_enum_field.py | 6 + src/bf_pktpy/library/fields/x_short_field.py | 6 + src/bf_pktpy/library/helpers/__init__.py | 0 src/bf_pktpy/library/helpers/bin.py | 50 ++ src/bf_pktpy/library/helpers/bytes2hex.py | 28 + src/bf_pktpy/library/helpers/chksum.py | 29 + src/bf_pktpy/library/helpers/constants.py | 21 + src/bf_pktpy/library/helpers/ether_types.py | 32 + src/bf_pktpy/library/helpers/get_if_list.py | 5 + src/bf_pktpy/library/helpers/ip.py | 80 +++ src/bf_pktpy/library/helpers/ip_types.py | 146 ++++ src/bf_pktpy/library/helpers/mac.py | 74 ++ src/bf_pktpy/library/specs/__init__.py | 25 + src/bf_pktpy/library/specs/base.py | 640 ++++++++++++++++++ src/bf_pktpy/library/specs/bfd.py | 38 ++ src/bf_pktpy/library/specs/bootp.py | 35 + src/bf_pktpy/library/specs/constant.py | 23 + src/bf_pktpy/library/specs/container.py | 61 ++ src/bf_pktpy/library/specs/dhcp.py | 32 + src/bf_pktpy/library/specs/dot1q.py | 55 ++ src/bf_pktpy/library/specs/ethernet.py | 83 +++ .../library/specs/extends/__init__.py | 0 .../library/specs/extends/l4checksum.py | 33 + src/bf_pktpy/library/specs/gre.py | 41 ++ src/bf_pktpy/library/specs/icmp.py | 36 + src/bf_pktpy/library/specs/ipv4.py | 55 ++ src/bf_pktpy/library/specs/ipv6.py | 55 ++ src/bf_pktpy/library/specs/packet.py | 239 +++++++ src/bf_pktpy/library/specs/pretty.py | 308 +++++++++ src/bf_pktpy/library/specs/tcp.py | 38 ++ .../library/specs/templates/__init__.py | 81 +++ src/bf_pktpy/library/specs/templates/arp.py | 130 ++++ src/bf_pktpy/library/specs/templates/bfd.py | 105 +++ src/bf_pktpy/library/specs/templates/bootp.py | 118 ++++ .../library/specs/templates/control.py | 46 ++ .../library/specs/templates/cpu/__init__.py | 41 ++ .../specs/templates/cpu/dtel_report_hdr.py | 50 ++ .../specs/templates/cpu/dtel_report_v2_hdr.py | 115 ++++ .../cpu/fabric_cpu_bfd_event_header.py | 31 + .../specs/templates/cpu/fabric_cpu_header.py | 44 ++ .../templates/cpu/fabric_cpu_sflow_header.py | 35 + .../cpu/fabric_cpu_timestamp_header.py | 34 + .../specs/templates/cpu/fabric_header.py | 43 ++ .../templates/cpu/fabric_multicast_header.py | 42 ++ .../templates/cpu/fabric_payload_header.py | 56 ++ .../templates/cpu/fabric_unicast_header.py | 37 + .../templates/cpu/mirror_pre_deparser.py | 20 + .../library/specs/templates/cpu/mod_header.py | 38 ++ .../specs/templates/cpu/postcard_header.py | 38 ++ .../cpu/simple_l3_mirror_cpu_header.py | 53 ++ src/bf_pktpy/library/specs/templates/dhcp.py | 224 ++++++ .../library/specs/templates/dot1ad.py | 46 ++ src/bf_pktpy/library/specs/templates/dot1q.py | 79 +++ .../specs/templates/erspan/__init__.py | 18 + .../templates/erspan/alternative/__init__.py | 0 .../templates/erspan/alternative/erspan.py | 101 +++ .../erspan/alternative/erspan_iii.py | 108 +++ .../erspan/alternative/platform_specific.py | 81 +++ .../library/specs/templates/erspan/erspan.py | 30 + .../specs/templates/erspan/erspan_ii.py | 67 ++ .../specs/templates/erspan/erspan_iii.py | 84 +++ .../erspan/erspan_platform_specific.py | 57 ++ .../library/specs/templates/ethernet.py | 110 +++ src/bf_pktpy/library/specs/templates/frame.py | 44 ++ src/bf_pktpy/library/specs/templates/gre.py | 117 ++++ src/bf_pktpy/library/specs/templates/gtpu.py | 81 +++ src/bf_pktpy/library/specs/templates/icmp.py | 93 +++ .../library/specs/templates/icmpv6_unknown.py | 66 ++ src/bf_pktpy/library/specs/templates/igmp.py | 93 +++ .../library/specs/templates/ipoption.py | 226 +++++++ src/bf_pktpy/library/specs/templates/ipv4.py | 287 ++++++++ src/bf_pktpy/library/specs/templates/ipv6.py | 234 +++++++ .../specs/templates/ipv6_ext_hdr_routing.py | 125 ++++ src/bf_pktpy/library/specs/templates/mpls.py | 52 ++ .../library/specs/templates/payload.py | 48 ++ src/bf_pktpy/library/specs/templates/raw.py | 13 + .../library/specs/templates/sfc/__init__.py | 26 + .../mac_control_class_based_flow_control.py | 43 ++ .../specs/templates/sfc/sfc_cpu_header.py | 34 + .../specs/templates/sfc/sfc_fabric_header.py | 29 + .../library/specs/templates/sfc/sfc_pause.py | 29 + .../library/specs/templates/sfc/sfc_roce.py | 150 ++++ src/bf_pktpy/library/specs/templates/tcp.py | 205 ++++++ .../library/specs/templates/tcpoption.py | 52 ++ src/bf_pktpy/library/specs/templates/udp.py | 130 ++++ src/bf_pktpy/library/specs/templates/vxlan.py | 75 ++ .../library/specs/templates/xnt/__init__.py | 15 + .../specs/templates/xnt/int_l45_head.py | 21 + .../specs/templates/xnt/int_l45_tail.py | 16 + .../library/specs/templates/xnt/int_meta.py | 23 + src/bf_pktpy/library/specs/udp.py | 36 + src/bf_pktpy/library/specs/validate.py | 334 +++++++++ .../library/specs/validate_sport_dport.py | 46 ++ .../library/specs/validate_src_dst.py | 40 ++ src/bf_pktpy/library/utils/__init__.py | 24 + src/bf_pktpy/library/utils/answer.py | 56 ++ .../library/utils/bridge_and_sniff.py | 162 +++++ src/bf_pktpy/library/utils/decoder.py | 92 +++ src/bf_pktpy/library/utils/hexdump.py | 86 +++ src/bf_pktpy/library/utils/interface.py | 233 +++++++ src/bf_pktpy/library/utils/listener.py | 105 +++ src/bf_pktpy/library/utils/ls.py | 151 +++++ src/bf_pktpy/library/utils/sniff.py | 167 +++++ src/bf_pktpy/library/utils/stream.py | 153 +++++ src/bf_pktpy/library/utils/tool.py | 47 ++ src/bf_pktpy/library/validators/__init__.py | 11 + src/bf_pktpy/main.py | 20 + src/bf_pktpy/packets/__init__.py | 35 + src/bf_pktpy/ptf/__init__.py | 0 src/bf_pktpy/ptf/packet_pktpy.py | 121 ++++ 144 files changed, 10027 insertions(+) create mode 100644 src/bf_pktpy/__init__.py create mode 100644 src/bf_pktpy/__main__.py create mode 100644 src/bf_pktpy/all/__init__.py create mode 100644 src/bf_pktpy/build_information.py create mode 100644 src/bf_pktpy/commands.py create mode 100644 src/bf_pktpy/library/__init__.py create mode 100644 src/bf_pktpy/library/fields/__init__.py create mode 100644 src/bf_pktpy/library/fields/bit_enum_field.py create mode 100644 src/bf_pktpy/library/fields/bit_field.py create mode 100644 src/bf_pktpy/library/fields/byte_enum_field.py create mode 100644 src/bf_pktpy/library/fields/byte_field.py create mode 100644 src/bf_pktpy/library/fields/conditional_field.py create mode 100644 src/bf_pktpy/library/fields/dest_ip_field.py create mode 100644 src/bf_pktpy/library/fields/dest_mac_field.py create mode 100644 src/bf_pktpy/library/fields/enum_field.py create mode 100644 src/bf_pktpy/library/fields/field.py create mode 100644 src/bf_pktpy/library/fields/flag_value.py create mode 100644 src/bf_pktpy/library/fields/flags_field.py create mode 100644 src/bf_pktpy/library/fields/int_field.py create mode 100644 src/bf_pktpy/library/fields/ip_field.py create mode 100644 src/bf_pktpy/library/fields/ip_list_field.py create mode 100644 src/bf_pktpy/library/fields/ipoptions_list_field.py create mode 100644 src/bf_pktpy/library/fields/mac_field.py create mode 100644 src/bf_pktpy/library/fields/short_enum_field.py create mode 100644 src/bf_pktpy/library/fields/short_field.py create mode 100644 src/bf_pktpy/library/fields/source_ip_field.py create mode 100644 src/bf_pktpy/library/fields/source_mac_field.py create mode 100644 src/bf_pktpy/library/fields/str_field.py create mode 100644 src/bf_pktpy/library/fields/three_bytes_field.py create mode 100644 src/bf_pktpy/library/fields/x_3byte_field.py create mode 100644 src/bf_pktpy/library/fields/x_bit_field.py create mode 100644 src/bf_pktpy/library/fields/x_byte_field.py create mode 100644 src/bf_pktpy/library/fields/x_int_field.py create mode 100644 src/bf_pktpy/library/fields/x_long_field.py create mode 100644 src/bf_pktpy/library/fields/x_short_enum_field.py create mode 100644 src/bf_pktpy/library/fields/x_short_field.py create mode 100644 src/bf_pktpy/library/helpers/__init__.py create mode 100644 src/bf_pktpy/library/helpers/bin.py create mode 100644 src/bf_pktpy/library/helpers/bytes2hex.py create mode 100644 src/bf_pktpy/library/helpers/chksum.py create mode 100644 src/bf_pktpy/library/helpers/constants.py create mode 100644 src/bf_pktpy/library/helpers/ether_types.py create mode 100644 src/bf_pktpy/library/helpers/get_if_list.py create mode 100644 src/bf_pktpy/library/helpers/ip.py create mode 100644 src/bf_pktpy/library/helpers/ip_types.py create mode 100644 src/bf_pktpy/library/helpers/mac.py create mode 100644 src/bf_pktpy/library/specs/__init__.py create mode 100644 src/bf_pktpy/library/specs/base.py create mode 100644 src/bf_pktpy/library/specs/bfd.py create mode 100644 src/bf_pktpy/library/specs/bootp.py create mode 100644 src/bf_pktpy/library/specs/constant.py create mode 100644 src/bf_pktpy/library/specs/container.py create mode 100644 src/bf_pktpy/library/specs/dhcp.py create mode 100644 src/bf_pktpy/library/specs/dot1q.py create mode 100644 src/bf_pktpy/library/specs/ethernet.py create mode 100644 src/bf_pktpy/library/specs/extends/__init__.py create mode 100644 src/bf_pktpy/library/specs/extends/l4checksum.py create mode 100644 src/bf_pktpy/library/specs/gre.py create mode 100644 src/bf_pktpy/library/specs/icmp.py create mode 100644 src/bf_pktpy/library/specs/ipv4.py create mode 100644 src/bf_pktpy/library/specs/ipv6.py create mode 100644 src/bf_pktpy/library/specs/packet.py create mode 100644 src/bf_pktpy/library/specs/pretty.py create mode 100644 src/bf_pktpy/library/specs/tcp.py create mode 100644 src/bf_pktpy/library/specs/templates/__init__.py create mode 100644 src/bf_pktpy/library/specs/templates/arp.py create mode 100644 src/bf_pktpy/library/specs/templates/bfd.py create mode 100644 src/bf_pktpy/library/specs/templates/bootp.py create mode 100644 src/bf_pktpy/library/specs/templates/control.py create mode 100644 src/bf_pktpy/library/specs/templates/cpu/__init__.py create mode 100644 src/bf_pktpy/library/specs/templates/cpu/dtel_report_hdr.py create mode 100644 src/bf_pktpy/library/specs/templates/cpu/dtel_report_v2_hdr.py create mode 100644 src/bf_pktpy/library/specs/templates/cpu/fabric_cpu_bfd_event_header.py create mode 100644 src/bf_pktpy/library/specs/templates/cpu/fabric_cpu_header.py create mode 100644 src/bf_pktpy/library/specs/templates/cpu/fabric_cpu_sflow_header.py create mode 100644 src/bf_pktpy/library/specs/templates/cpu/fabric_cpu_timestamp_header.py create mode 100644 src/bf_pktpy/library/specs/templates/cpu/fabric_header.py create mode 100644 src/bf_pktpy/library/specs/templates/cpu/fabric_multicast_header.py create mode 100644 src/bf_pktpy/library/specs/templates/cpu/fabric_payload_header.py create mode 100644 src/bf_pktpy/library/specs/templates/cpu/fabric_unicast_header.py create mode 100644 src/bf_pktpy/library/specs/templates/cpu/mirror_pre_deparser.py create mode 100644 src/bf_pktpy/library/specs/templates/cpu/mod_header.py create mode 100644 src/bf_pktpy/library/specs/templates/cpu/postcard_header.py create mode 100644 src/bf_pktpy/library/specs/templates/cpu/simple_l3_mirror_cpu_header.py create mode 100644 src/bf_pktpy/library/specs/templates/dhcp.py create mode 100644 src/bf_pktpy/library/specs/templates/dot1ad.py create mode 100644 src/bf_pktpy/library/specs/templates/dot1q.py create mode 100644 src/bf_pktpy/library/specs/templates/erspan/__init__.py create mode 100644 src/bf_pktpy/library/specs/templates/erspan/alternative/__init__.py create mode 100644 src/bf_pktpy/library/specs/templates/erspan/alternative/erspan.py create mode 100644 src/bf_pktpy/library/specs/templates/erspan/alternative/erspan_iii.py create mode 100644 src/bf_pktpy/library/specs/templates/erspan/alternative/platform_specific.py create mode 100644 src/bf_pktpy/library/specs/templates/erspan/erspan.py create mode 100644 src/bf_pktpy/library/specs/templates/erspan/erspan_ii.py create mode 100644 src/bf_pktpy/library/specs/templates/erspan/erspan_iii.py create mode 100644 src/bf_pktpy/library/specs/templates/erspan/erspan_platform_specific.py create mode 100644 src/bf_pktpy/library/specs/templates/ethernet.py create mode 100644 src/bf_pktpy/library/specs/templates/frame.py create mode 100644 src/bf_pktpy/library/specs/templates/gre.py create mode 100644 src/bf_pktpy/library/specs/templates/gtpu.py create mode 100644 src/bf_pktpy/library/specs/templates/icmp.py create mode 100644 src/bf_pktpy/library/specs/templates/icmpv6_unknown.py create mode 100644 src/bf_pktpy/library/specs/templates/igmp.py create mode 100644 src/bf_pktpy/library/specs/templates/ipoption.py create mode 100644 src/bf_pktpy/library/specs/templates/ipv4.py create mode 100644 src/bf_pktpy/library/specs/templates/ipv6.py create mode 100644 src/bf_pktpy/library/specs/templates/ipv6_ext_hdr_routing.py create mode 100644 src/bf_pktpy/library/specs/templates/mpls.py create mode 100644 src/bf_pktpy/library/specs/templates/payload.py create mode 100644 src/bf_pktpy/library/specs/templates/raw.py create mode 100644 src/bf_pktpy/library/specs/templates/sfc/__init__.py create mode 100644 src/bf_pktpy/library/specs/templates/sfc/mac_control_class_based_flow_control.py create mode 100644 src/bf_pktpy/library/specs/templates/sfc/sfc_cpu_header.py create mode 100644 src/bf_pktpy/library/specs/templates/sfc/sfc_fabric_header.py create mode 100644 src/bf_pktpy/library/specs/templates/sfc/sfc_pause.py create mode 100644 src/bf_pktpy/library/specs/templates/sfc/sfc_roce.py create mode 100644 src/bf_pktpy/library/specs/templates/tcp.py create mode 100644 src/bf_pktpy/library/specs/templates/tcpoption.py create mode 100644 src/bf_pktpy/library/specs/templates/udp.py create mode 100644 src/bf_pktpy/library/specs/templates/vxlan.py create mode 100644 src/bf_pktpy/library/specs/templates/xnt/__init__.py create mode 100644 src/bf_pktpy/library/specs/templates/xnt/int_l45_head.py create mode 100644 src/bf_pktpy/library/specs/templates/xnt/int_l45_tail.py create mode 100644 src/bf_pktpy/library/specs/templates/xnt/int_meta.py create mode 100644 src/bf_pktpy/library/specs/udp.py create mode 100644 src/bf_pktpy/library/specs/validate.py create mode 100644 src/bf_pktpy/library/specs/validate_sport_dport.py create mode 100644 src/bf_pktpy/library/specs/validate_src_dst.py create mode 100644 src/bf_pktpy/library/utils/__init__.py create mode 100644 src/bf_pktpy/library/utils/answer.py create mode 100644 src/bf_pktpy/library/utils/bridge_and_sniff.py create mode 100644 src/bf_pktpy/library/utils/decoder.py create mode 100644 src/bf_pktpy/library/utils/hexdump.py create mode 100644 src/bf_pktpy/library/utils/interface.py create mode 100644 src/bf_pktpy/library/utils/listener.py create mode 100644 src/bf_pktpy/library/utils/ls.py create mode 100644 src/bf_pktpy/library/utils/sniff.py create mode 100644 src/bf_pktpy/library/utils/stream.py create mode 100644 src/bf_pktpy/library/utils/tool.py create mode 100644 src/bf_pktpy/library/validators/__init__.py create mode 100644 src/bf_pktpy/main.py create mode 100644 src/bf_pktpy/packets/__init__.py create mode 100644 src/bf_pktpy/ptf/__init__.py create mode 100644 src/bf_pktpy/ptf/packet_pktpy.py diff --git a/src/bf_pktpy/__init__.py b/src/bf_pktpy/__init__.py new file mode 100644 index 0000000..db6c73a --- /dev/null +++ b/src/bf_pktpy/__init__.py @@ -0,0 +1,19 @@ +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from bf_pktpy.library.specs import Ether, IP, IPv6, ARP, TCP, UDP, ICMP, MPLS +from bf_pktpy.library.specs import GRE, Dot1Q, BOOTP, DHCP, BFD + +# from bf_pktpy.library.specs import Arp +from bf_pktpy.library.utils import Interface, Stream, Listener, Decoder +from bf_pktpy.commands import send, sendp, sr, sr1, srp, srp1 +from bf_pktpy.commands import srloop, srploop, sniff, bridge_and_sniff diff --git a/src/bf_pktpy/__main__.py b/src/bf_pktpy/__main__.py new file mode 100644 index 0000000..0a14afc --- /dev/null +++ b/src/bf_pktpy/__main__.py @@ -0,0 +1,17 @@ +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +from main import run_interactive + +if __name__ == "__main__": + run_interactive() diff --git a/src/bf_pktpy/all/__init__.py b/src/bf_pktpy/all/__init__.py new file mode 100644 index 0000000..f2ca1be --- /dev/null +++ b/src/bf_pktpy/all/__init__.py @@ -0,0 +1,19 @@ +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +# Import all from packets & utils +from bf_pktpy.packets import * +from bf_pktpy.library.helpers.bytes2hex import bytes2hex +from bf_pktpy.library.helpers.get_if_list import get_if_list +from bf_pktpy.library.utils.hexdump import hexdump +from bf_pktpy.library.utils.ls import ls diff --git a/src/bf_pktpy/build_information.py b/src/bf_pktpy/build_information.py new file mode 100644 index 0000000..d591d1d --- /dev/null +++ b/src/bf_pktpy/build_information.py @@ -0,0 +1,23 @@ +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from bf_pktpy.library.helpers import constants + + +class BuildInformation: + def __init__(self): + self._version = "0.2" + + def show_details(self, logo=False): + if logo: + print(constants.logo) + print("\tBarefoot PKTPY (Packet Generator)\n\tVersion: %s" % self._version) diff --git a/src/bf_pktpy/commands.py b/src/bf_pktpy/commands.py new file mode 100644 index 0000000..b403aec --- /dev/null +++ b/src/bf_pktpy/commands.py @@ -0,0 +1,408 @@ +#!/usr/bin/env python + + +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" bf_pktpy python library """ +import socket +import time +from bf_pktpy.library.utils import Interface, Stream, Listener, Decoder +from bf_pktpy.library.utils import Answer, Unanswer, Sniffer, Received +from bf_pktpy.library.utils import BridgeSniff + + +# ============================================================================= +def send(packets, **kwargs): + """Send packets at layer 3 + + Args: + packets (list): list of packets + inter (int): time in sec between 2 packets (default 0) + loop (int): send packet indefinetly (default 0) + count (int): number of packets to send (default -1) + verbose (bool): verbose mode + realtime (int): check pkt was sent before sending next one + return_packets (bool): return the sent packets + socket (obj): the socket to use + iface (str): the interface to send the packets on + Returns: + bool: None + Examples: + | send(packets) + """ + + inter = kwargs.pop("inter", 0) + loop = kwargs.pop("loop", 0) + count = kwargs.pop("count", -1) + verbose = kwargs.pop("verbose", False) + realtime = kwargs.pop("realtime", None) + return_packets = kwargs.pop("return_packets", False) + sock = kwargs.pop("socket", None) + iface = kwargs.pop("iface", None) + + if not isinstance(packets, (list, tuple)): + packets = [packets] + + interface = Interface.select(iface) + sock_ = sock + + if not sock: + try: + sock_ = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_RAW) + except socket.error: + print("ERROR- Cannot create a socket") + raise + if not kwargs.get("noprint", False) and kwargs.get("verbose", True): + print("Send %s packet(s)" % len(packets)) + + stream = Stream(packets, interface) + result = stream.send( + sock_, + inter=inter, + loop=loop, + count=count, + verbose=verbose, + realtime=realtime, + return_packets=return_packets, + ) + if verbose: + print(result) + + if not sock: + sock_.close() + return result + + +def sendp(packets, **kwargs): + """Send packets at layer 2 + + Args: + packets (list): list of packets + inter (int): time in sec between 2 packets (default 0) + loop (int): send packet indefinetly (default 0) + count (int): number of packets to send (default None=1) + verbose (bool): verbose mode + realtime (int): check pkt was sent before sending next one + return_packets (bool): return the sent packets + socket (obj): the socket to use + iface (str): the interface to send the packets on + Returns: + bool: None + Examples: + | send(packets) + """ + inter = kwargs.pop("inter", 0) + loop = kwargs.pop("loop", 0) + count = kwargs.pop("count", -1) + verbose = kwargs.pop("verbose", False) + realtime = kwargs.pop("realtime", None) + return_packets = kwargs.pop("return_packets", False) + sock = kwargs.pop("socket", None) + iface = kwargs.pop("iface", None) + + if not isinstance(packets, (list, tuple)): + packets = [packets] + + interface = Interface.select(iface) + sock_ = sock + + if not sock: + try: + sock_ = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(3)) + except socket.error: + print("ERROR- Cannot create a socket") + raise + sock_.bind((interface, 0)) + + if not kwargs.get("noprint", False) and kwargs.get("verbose", True): + print("Send %s packet(s)" % len(packets)) + + stream = Stream(packets, interface) + result = stream.send( + sock_, + inter=inter, + loop=loop, + count=count, + verbose=verbose, + realtime=realtime, + return_packets=return_packets, + ) + if verbose: + print(result) + + if not sock: + sock_.close() + + return result + + +def sr(packets, **kwargs): + """Send and receive packets at layer 3 + + Args: + packets (list): list of packets + promisc (bool): promiscuous mode + filter (int): filter + iface (str): the interface to send the packets on + nofilter (int): no filter + Returns: + tuple: (, ) objects + Examples: + | sr(packets) + """ + + # select interface + iface = kwargs.pop("iface", None) + interface = Interface.select(iface) + kwargs.update({"iface": interface}) + + # promisc = kwargs.pop("promisc", False) + # _filter = kwargs.pop("filter", None) + # iface = kwargs.pop("iface", None) + # nofilter = kwargs.pop("nofilter", 0) + timeout = kwargs.get("timeout", 1) + + # Setup listener + listener = Listener(interface, **kwargs) + listener.start(count=kwargs.get("count", 0)) + + # Send packets + if kwargs.get("verbose", True): + print("Begin emission:") + send(packets, noprint=True, **kwargs) + if kwargs.get("verbose", True): + print("Finish to send %s packet(s)" % len(packets)) + + answered, unanswered = Answer(), Unanswer() + + # Receive with expiration + time.sleep(timeout) + result = listener.received() + if not result: + return None, None + + matches = [] + intf_addr = None + for packet in packets: + # check if layer4 is TCP or UDP + if packet.proto in (6, 17): + layer4 = packet.body + matches.append( + (packet.src, packet.dst, packet.proto, layer4.sport, layer4.dport) + ) + else: + matches.append((packet.src, packet.dst, packet.proto)) + if intf_addr is None: + intf_addr = packet.src + + # look for matching src IP address + rcvd_pkts = 0 + markers = [1] * len(packets) + for raw in result: + decoded = Decoder(raw) + if decoded.is_protocol("IPv4"): + layer3 = decoded.layer3 + if layer3.dst == intf_addr: + rcvd_pkts += 1 + if layer3.proto in (6, 17): + layer4 = decoded.layer4 + to_match = ( + layer3.dst, + layer3.src, + layer3.proto, + layer4.dport, + layer4.sport, + ) + else: + to_match = (layer3.dst, layer3.src, layer3.proto) + if to_match in matches: + sent = packets[matches.index(to_match)] + answered.append((sent, decoded)) + markers[matches.index(to_match)] = 0 + if packets: + for idx, packet in enumerate(packets): + if markers[idx]: + unanswered.append(packet) + + if kwargs.get("verbose", True): + print( + "Received %s packets, got %s answered, remaining %s packets" + % (rcvd_pkts, len(answered), len(unanswered)) + ) + + return answered, unanswered + + +def sr1(packets, **kwargs): + """Send and receive packet at layer 3. Return only 1st answer""" + ans, _ = sr(packets, **kwargs) + if ans: + return ans[0] + return None + + +def srp(packets, **kwargs): + """Send and receive packet at layer 2 + + Args: + packets (list): list of packets + promisc (bool): promiscuous mode + filter (int): filter + iface (str): the interface to send the packets on + nofilter (int): no filter + Returns: + bool: None + Examples: + | sr(packet) + """ + + # select interface + iface = kwargs.pop("iface", None) + interface = Interface.select(iface) + kwargs.update({"iface": interface}) + + # promisc = kwargs.pop("promisc", False) + # _filter = kwargs.pop("filter", None) + # iface = kwargs.pop("iface", None) + # nofilter = kwargs.pop("nofilter", 0) + timeout = kwargs.get("timeout", 1) + + # Setup listener + listener = Listener(interface, **kwargs) + listener.start(count=kwargs.get("count", 0)) + + # Send packet + if kwargs.get("verbose", True): + print("Begin emission:") + sendp(packets, noprint=True, **kwargs) + if kwargs.get("verbose", True): + print("Finish to send 1 packet") + + answered, unanswered = Answer(), Unanswer() + + # Receive with expiration + time.sleep(timeout) + result = listener.received() + if not result: + return None, None + for raw in result: + decoded = Decoder(raw) + answered.append(decoded) + return answered, unanswered + + +def srp1(packets, **kwargs): + """Send and receive packet at layer 2. Return only 1st answer""" + ans, _ = srp(packets, **kwargs) + if ans: + return ans[0] + return None + + +def srloop(packets, **kwargs): + """Send and Receive packets in loop at layer 3""" + return _loop(sr, packets, **kwargs) + + +def srploop(packets, **kwargs): + """Send and Receive packets in loop at layer 2""" + return _loop(srp, packets, **kwargs) + + +def _loop(fun, packets, inter=1, count=None, store=True, **kwargs): + answered, unanswered = [], [] + try: + while True: + if count is not None: + if count == 0: + break + count -= 1 + + start = time.time() + ans, unans = fun(packets, **kwargs) + if store: + answered.append(ans) + unanswered.append(unans) + t_delta = time.time() - start + + if t_delta < inter: + time.sleep(inter + t_delta) + except KeyboardInterrupt: + pass + return answered, unanswered + + +def sniff(*args, **kwargs): + """Sniff packets + Args: + iface (str): interface or list of interfaces + filter (str): BPF filter to apply + store (bool): store or discard pkts + timeout (int): expire time + count (int): number of captured pkts; 0 means infinity + nofilter (int): no filter + prn (obj): function to apply to each packet + Returns: + obj: Received object + Examples: + | received = sniff(filter="tcp", count=2, timeout=3) + | received.summary() + """ + iface = kwargs.get("iface", None) + if iface is None: + interface = Interface.select() + kwargs.update({"iface": interface}) + sniffer = Sniffer(*args, **kwargs) + sniffer.start() + while not sniffer.is_completed(): + time.sleep(0.1) + + received = Received() + packets = sniffer.received() + received.extend(packets) + return received + + +def bridge_and_sniff( + if1, if2, xfrm12=None, xfrm21=None, prn=None, L2socket=None, *args, **kwargs +): + """Forward traffic between interfaces if1 and if2, sniff and return + the exchanged packets + Args: + if1 (str|obj): interface names or opened socket + if2 (str|obj): interface names or opened socket + xfrm12 (obj): fn to call when fwd pkt from if1 to if2 + xfrm21 (obj): fn to call when fwd pkt from if2 to if1 + prn (obj): function to apply to each packet + Returns: + obj: Received object + Examples: + | received = bridge_and_sniff(...) + | received.summary() + """ + kwargs.update( + {"xfrm12": xfrm12, "xfrm21": xfrm21, "prn": prn, "L2socket": L2socket} + ) + br_sniff = BridgeSniff(if1, if2, *args, **kwargs) + br_sniff.start() + while not br_sniff.is_completed(): + time.sleep(0.1) + + received = Received() + packets = br_sniff.received() + received.extend(packets) + return received + + +# ============================================================================= diff --git a/src/bf_pktpy/library/__init__.py b/src/bf_pktpy/library/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/bf_pktpy/library/fields/__init__.py b/src/bf_pktpy/library/fields/__init__.py new file mode 100644 index 0000000..5df497f --- /dev/null +++ b/src/bf_pktpy/library/fields/__init__.py @@ -0,0 +1,32 @@ +from bf_pktpy.library.fields.bit_field import BitField +from bf_pktpy.library.fields.byte_field import ByteField +from bf_pktpy.library.fields.short_field import ShortField +from bf_pktpy.library.fields.three_bytes_field import ThreeBytesField +from bf_pktpy.library.fields.int_field import IntField +from bf_pktpy.library.fields.field import Field +from bf_pktpy.library.fields.flags_field import FlagsField +from bf_pktpy.library.fields.mac_field import MACField +from bf_pktpy.library.fields.dest_mac_field import DestMACField +from bf_pktpy.library.fields.source_mac_field import SourceMACField +from bf_pktpy.library.fields.ip_field import IPField +from bf_pktpy.library.fields.dest_ip_field import DestIPField +from bf_pktpy.library.fields.source_ip_field import SourceIPField +from bf_pktpy.library.fields.str_field import StrField +from bf_pktpy.library.fields.enum_field import EnumField +from bf_pktpy.library.fields.x_short_enum_field import XShortEnumField +from bf_pktpy.library.fields.byte_enum_field import ByteEnumField +from bf_pktpy.library.fields.bit_enum_field import BitEnumField +from bf_pktpy.library.fields.x_bit_field import XBitField +from bf_pktpy.library.fields.x_byte_field import XByteField +from bf_pktpy.library.fields.x_short_field import XShortField +from bf_pktpy.library.fields.x_3byte_field import X3ByteField +from bf_pktpy.library.fields.x_int_field import XIntField +from bf_pktpy.library.fields.x_long_field import XLongField +from bf_pktpy.library.fields.enum_field import EnumField +from bf_pktpy.library.fields.x_short_enum_field import XShortEnumField +from bf_pktpy.library.fields.byte_enum_field import ByteEnumField +from bf_pktpy.library.fields.bit_enum_field import BitEnumField +from bf_pktpy.library.fields.short_enum_field import ShortEnumField +from bf_pktpy.library.fields.ip_list_field import IPListField +from bf_pktpy.library.fields.ipoptions_list_field import IPOptionsListField +from bf_pktpy.library.fields.conditional_field import ConditionalField diff --git a/src/bf_pktpy/library/fields/bit_enum_field.py b/src/bf_pktpy/library/fields/bit_enum_field.py new file mode 100644 index 0000000..176b216 --- /dev/null +++ b/src/bf_pktpy/library/fields/bit_enum_field.py @@ -0,0 +1,6 @@ +from bf_pktpy.library.fields import EnumField + + +class BitEnumField(EnumField): + def __init__(self, name, default_value, size=1, types=None): + super(BitEnumField, self).__init__(name, default_value, size, types) diff --git a/src/bf_pktpy/library/fields/bit_field.py b/src/bf_pktpy/library/fields/bit_field.py new file mode 100644 index 0000000..d401494 --- /dev/null +++ b/src/bf_pktpy/library/fields/bit_field.py @@ -0,0 +1,29 @@ +import math + +import six + +from bf_pktpy.library.fields.field import Field + + +# noinspection PyPropertyAccess,PyAttributeOutsideInit +class BitField(Field): + def __init__(self, name, default_value, size=1): + super(BitField, self).__init__(name, default_value, size) + + def from_internal(self, raw_value): + return super(BitField, self).from_internal(raw_value) + + def validate(self, new_value): + if new_value is None: + return True + + if isinstance(new_value, six.binary_type): + new_value = int.from_bytes(new_value, "big") + return isinstance(new_value, six.integer_types) and ( + 0 <= new_value <= math.pow(2, self.size) - 1 + ) + + def to_internal(self, new_value): + if isinstance(new_value, six.binary_type): + return int.from_bytes(new_value, "big") + return int(new_value) if new_value is not None else 0 diff --git a/src/bf_pktpy/library/fields/byte_enum_field.py b/src/bf_pktpy/library/fields/byte_enum_field.py new file mode 100644 index 0000000..34c7cd7 --- /dev/null +++ b/src/bf_pktpy/library/fields/byte_enum_field.py @@ -0,0 +1,6 @@ +from bf_pktpy.library.fields import EnumField + + +class ByteEnumField(EnumField): + def __init__(self, name, default_value, types=None): + super(ByteEnumField, self).__init__(name, default_value, size=8, types=types) diff --git a/src/bf_pktpy/library/fields/byte_field.py b/src/bf_pktpy/library/fields/byte_field.py new file mode 100644 index 0000000..aefbebd --- /dev/null +++ b/src/bf_pktpy/library/fields/byte_field.py @@ -0,0 +1,6 @@ +from bf_pktpy.library.fields.bit_field import BitField + + +class ByteField(BitField): + def __init__(self, name, default_value): + super(ByteField, self).__init__(name, default_value, size=8) diff --git a/src/bf_pktpy/library/fields/conditional_field.py b/src/bf_pktpy/library/fields/conditional_field.py new file mode 100644 index 0000000..0577233 --- /dev/null +++ b/src/bf_pktpy/library/fields/conditional_field.py @@ -0,0 +1,35 @@ +from bf_pktpy.library.fields import Field + + +class ConditionalField(Field): + def __init__(self, field_def, condition): + """A field container for any field and a condition. + + If the condition is met, the underlying field will be taken into account when + generating _members and printing representation of header (repr). Condition + should be in form of callable, which takes a Packet obj (or any child) and + returns boolean value. + + :param field_def: a Field object (or any child) + :type field_def: Field + :param condition: a boolean callable which takes a Packet obj (or any child) + """ + self.field = field_def + self.condition = condition + # We want to pass internal default value and size of underlying field in case + # of callables + super(ConditionalField, self).__init__( + field_def.name, field_def._default_value, field_def._size + ) + + def from_internal(self, raw_value): + return self.field.from_internal(raw_value) + + def to_internal(self, new_value): + return self.field.to_internal(new_value) + + def validate(self, new_value): + return self.field.validate(new_value) + + def post_build(self, pkt): + self.field.post_build(pkt) diff --git a/src/bf_pktpy/library/fields/dest_ip_field.py b/src/bf_pktpy/library/fields/dest_ip_field.py new file mode 100644 index 0000000..d859078 --- /dev/null +++ b/src/bf_pktpy/library/fields/dest_ip_field.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python + + +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" DestIPField """ +from bf_pktpy.library.fields.ip_field import IPField + + +class DestIPField(IPField): + def __init__(self, name, default_value=None): + super(DestIPField, self).__init__(name, default_value) diff --git a/src/bf_pktpy/library/fields/dest_mac_field.py b/src/bf_pktpy/library/fields/dest_mac_field.py new file mode 100644 index 0000000..4fcab71 --- /dev/null +++ b/src/bf_pktpy/library/fields/dest_mac_field.py @@ -0,0 +1,6 @@ +from bf_pktpy.library.fields.mac_field import MACField + + +class DestMACField(MACField): + def __init__(self, name, default_value="ff:ff:ff:ff:ff:ff"): + super(DestMACField, self).__init__(name, default_value) diff --git a/src/bf_pktpy/library/fields/enum_field.py b/src/bf_pktpy/library/fields/enum_field.py new file mode 100644 index 0000000..eb4192c --- /dev/null +++ b/src/bf_pktpy/library/fields/enum_field.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python + + +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" EnumField """ +import math +from operator import attrgetter +import six + +from bf_pktpy.library.fields import Field + + +# noinspection PyPropertyAccess,PyAttributeOutsideInit +class EnumField(Field): + """EnumField adds another property: `types` which holds valid int:str mapping + for the field. + """ + + def __init__(self, name, default_value, size, types=None): + super(EnumField, self).__init__(name, default_value, size) + self.types = types + + types = property(attrgetter("_types")) + + @types.setter + def types(self, types): + if isinstance(types, dict): + self._types = types + elif types is None: + self._types = {} + else: + raise TypeError( + "Types in enum field should be dictionary not: {}".format(type(types)) + ) + + def __repr__(self): + return "{}(name={}, default_value={}, length={})".format( + type(self).__name__, + self.name, + self._get_hex_value(self.default_value), + self.size, + ) + + def _get_hex_value(self, value): + hex_value = self.defaultvalue2hex() + return self.types[hex_value] if hex_value in self.types else hex(value) + + def from_internal(self, raw_value): + return super(EnumField, self).from_internal(raw_value) + + def validate(self, new_value): + if new_value is None: + return True + if isinstance(new_value, six.string_types): + try: + _ = int(new_value, 0) + return True + except (TypeError, ValueError): + return False + return isinstance(new_value, six.integer_types) and ( + 0 <= new_value <= math.pow(2, self.size) - 1 + ) + + def to_internal(self, new_value): + if new_value is None: + return 0 + if isinstance(new_value, six.string_types): + return int(new_value, 0) + return int(new_value) diff --git a/src/bf_pktpy/library/fields/field.py b/src/bf_pktpy/library/fields/field.py new file mode 100644 index 0000000..f623194 --- /dev/null +++ b/src/bf_pktpy/library/fields/field.py @@ -0,0 +1,138 @@ +import abc +from abc import ABCMeta +import math +from operator import attrgetter + + +# noinspection PyPropertyAccess,PyAttributeOutsideInit +class Field(object): + """Abstract class for defining validation field. + + In order to define a field, one needs to implement 3 methods:\n + * `from_internal`\n + * `to_internal`\n + * `validate`\n + + There is also an option to implement `post_build` method in order to invoke some + operations after the packet which defines field of that type is created. + + As a default, one can give a strict value, or provide a function to + invoke when field is asked for its default value. The signature of this function + should be (it can be lambda function): + + def (packet: Packet) -> Any + """ + + __metaclass__ = ABCMeta + + def __init__(self, name, default_value, size): + self._name = "" + self._default_value = None + self._size = 0 + + self.name = name + self.size = size + self.default_value = default_value + + def __str__(self): + return self.__repr__() + + def __repr__(self): + return "{}(name={}, default_value={}, length={})".format( + type(self).__name__, + self.name, + self._default_value.__name__ + if callable(self._default_value) + else self.default_value, + self.size, + ) + + name = property(attrgetter("_name")) + + @name.setter + def name(self, new_name): + if isinstance(new_name, str): + self._name = new_name + else: + raise TypeError("Value %s is not a string" % new_name) + + @property + def default_value(self): + if self._default_value is None or callable(self._default_value): + return None + return self.from_internal(self._default_value) + + @default_value.setter + def default_value(self, new_value): + if new_value is None or callable(new_value): + self._default_value = new_value + return + if not self.validate(new_value): + raise TypeError( + "Value %s is not valid for field of type %s" % (new_value, self.name) + ) + self._default_value = self.to_internal(new_value) + + size = property(attrgetter("_size")) + + @size.setter + def size(self, new_size): + if (isinstance(new_size, int) and new_size > 0) or callable(new_size): + self._size = new_size + else: + raise TypeError("Given size %s is not a valid positive int" % new_size) + + def value2bin(self, value): + return bin(value)[2:].zfill(self.size) + + def defaultvalue2bin(self): + return self.value2bin(self.default_value) + + def value2hex(self, value): + if value is None: + value = 0 + return hex(value)[2:].zfill(int(math.ceil(self.size / 4.0))) + + def defaultvalue2hex(self): + return self.value2hex(self.default_value) + + def post_build(self, pkt): + """An option to perform some actions after the given packet is created. + + :param pkt: a packet object + :type pkt: bf_pktpy.library.specs.packet.Packet + """ + pass + + @abc.abstractmethod + def from_internal(self, raw_value): + """Returns value transformed from its raw representation (usually int). + + :param raw_value: internal representation of value of the field + :type raw_value: Any + :return: transformed value of the field in its normal representation + :rtype: Any + """ + return raw_value + + @abc.abstractmethod + def to_internal(self, new_value): + """Returns internal representation of value of the field (usually int). + + :param new_value: value of the field in its normal representation + :type new_value: Any + :return: internal representation of value of the field + :rtype: Any + """ + return int(new_value) + + @abc.abstractmethod + def validate(self, new_value): + """Validates new_value against rules defined in the field. + + :param new_value: value of the field in its normal representation + :type new_value: Any + :return: validation result + :rtype: bool + """ + return True diff --git a/src/bf_pktpy/library/fields/flag_value.py b/src/bf_pktpy/library/fields/flag_value.py new file mode 100644 index 0000000..89f8be3 --- /dev/null +++ b/src/bf_pktpy/library/fields/flag_value.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python + + +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" FlagValue class """ + + +class FlagValue(object): + def __init__(self, value, types): + self.value = value + self.types = types + + def __repr__(self): + delimiter = "+" if isinstance(self.types, list) else "" + bin_val = bin(self.value)[2:].zfill(len(self.types)) + flags_str = delimiter.join( + flag for bit, flag in zip(bin_val[::-1], self.types) if int(bit) + ) + return "" % (self.value, flags_str) diff --git a/src/bf_pktpy/library/fields/flags_field.py b/src/bf_pktpy/library/fields/flags_field.py new file mode 100644 index 0000000..672469e --- /dev/null +++ b/src/bf_pktpy/library/fields/flags_field.py @@ -0,0 +1,68 @@ +import functools +import math +import six + +from bf_pktpy.library.fields.field import Field +from bf_pktpy.library.fields.flag_value import FlagValue + + +# noinspection PyPropertyAccess +class FlagsField(Field): + """FlagsField adds another property: `flags` which can store flag information. + + Provided flags should be in one of two supported variants: + + Example (list): + >>> from bf_pktpy.all import Packet + >>> class FlagsPacket(Packet): + fields_desc = [FlagsField("list_flags", 0, 3, ["foo", "bar", "baz"])] + >>> FlagsPacket(list_flags=3).list_flags + 'foo+bar' + + Example (str): + >>> from bf_pktpy.all import Packet + >>> class FlagsPacket(Packet): + fields_desc = [FlagsField("list_flags", 5, 4, "BASE")] + >>> FlagsPacket(list_flags=3).list_flags + 'BA' + + """ + + def __init__(self, name, default_value, size, flags): + if not isinstance(flags, (str, list)): + raise TypeError( + "Flags provided: %s are not of type str or list" % str(flags) + ) + super(FlagsField, self).__init__(name, default_value, size) + self.flags = flags + + def to_internal(self, new_value): + if not new_value: + return 0 + + if isinstance(new_value, six.integer_types): + return new_value + + if isinstance(new_value, FlagValue): + return new_value.value + + new_flags = new_value.split("+") if isinstance(self.flags, list) else new_value + return functools.reduce( + lambda x, y: x + y, (1 << self.flags.index(flag) for flag in new_flags) + ) + + def validate(self, new_value): + if new_value is None or ( + isinstance(new_value, six.integer_types) + and (0 <= new_value <= math.pow(2, self.size) - 1) + ): + return True + + if not isinstance(new_value, (str, list)): + return False + + new_flags = new_value.split("+") if isinstance(self.flags, list) else new_value + return all(flag in self.flags for flag in set(new_flags)) + + def from_internal(self, raw_value): + return FlagValue(raw_value, self.flags) diff --git a/src/bf_pktpy/library/fields/int_field.py b/src/bf_pktpy/library/fields/int_field.py new file mode 100644 index 0000000..87e240d --- /dev/null +++ b/src/bf_pktpy/library/fields/int_field.py @@ -0,0 +1,6 @@ +from bf_pktpy.library.fields.bit_field import BitField + + +class IntField(BitField): + def __init__(self, name, default_value): + super(IntField, self).__init__(name, default_value, size=32) diff --git a/src/bf_pktpy/library/fields/ip_field.py b/src/bf_pktpy/library/fields/ip_field.py new file mode 100644 index 0000000..108e00c --- /dev/null +++ b/src/bf_pktpy/library/fields/ip_field.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python + + +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" IPField """ +import ipaddress +import six +import socket + +from scapy_helper import int2ip, ip2int + +from bf_pktpy.library.fields import Field + + +# noinspection PyPropertyAccess,PyAttributeOutsideInit +class IPField(Field): + def __init__(self, name, default_value): + super(IPField, self).__init__(name, default_value, size=32) + + def from_internal(self, raw_value): + return int2ip(raw_value) + + def to_internal(self, new_value): + if new_value is None: + new_value = "127.0.0.1" + if isinstance(new_value, six.string_types): + return ip2int(new_value) + if isinstance(new_value, six.binary_type): + return int.from_bytes(new_value, "big") + return int(new_value) + + def validate(self, new_value): + if new_value is None: + return True + if isinstance(new_value, six.integer_types): + try: + _ = int2ip(new_value) + return True + except (TypeError, socket.error): + return False + if isinstance(new_value, six.string_types): + try: + _ = ipaddress.IPv4Address(six.ensure_text(new_value)) + return True + except ipaddress.AddressValueError: + return False + if isinstance(new_value, six.binary_type): + try: + _ = ipaddress.IPv4Address(new_value) + return True + except ipaddress.AddressValueError: + return False + + return False diff --git a/src/bf_pktpy/library/fields/ip_list_field.py b/src/bf_pktpy/library/fields/ip_list_field.py new file mode 100644 index 0000000..6007c55 --- /dev/null +++ b/src/bf_pktpy/library/fields/ip_list_field.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python + + +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +from bf_pktpy.library.fields.ip_field import IPField + + +# noinspection PyPropertyAccess,PyAttributeOutsideInit +class IPListField(IPField): + def __init__(self, name, default_value=None): + if default_value is None: + default_value = [] + if not isinstance(default_value, list): + default_value = [default_value] + # We are omitting IPField constructor, as size will be different than 32 + super(IPField, self).__init__( + name, default_value, size=lambda value: value * 32 + ) + + def from_internal(self, raw_value): + return [super(IPListField, self).from_internal(raw_ip) for raw_ip in raw_value] + + def to_internal(self, new_value): + if new_value is None: + return [] + if not isinstance(new_value, list): + new_value = [new_value] + return [super(IPListField, self).to_internal(new_ip) for new_ip in new_value] + + def validate(self, new_value): + if new_value is None: + return True + if not isinstance(new_value, list): + new_value = [new_value] + return all(super(IPListField, self).validate(new_ip) for new_ip in new_value) diff --git a/src/bf_pktpy/library/fields/ipoptions_list_field.py b/src/bf_pktpy/library/fields/ipoptions_list_field.py new file mode 100644 index 0000000..ab60c00 --- /dev/null +++ b/src/bf_pktpy/library/fields/ipoptions_list_field.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python + + +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +import six + +from bf_pktpy.library.specs.templates.ipoption import ( + _IPOption, + IPOption, + ipoptions_mapping, +) +from bf_pktpy.library.fields.field import Field + + +# noinspection PyPropertyAccess,PyAttributeOutsideInit +class IPOptionsListField(Field): + def __init__(self, name, default_value=None): + if default_value is None: + default_value = [] + if not isinstance(default_value, list): + default_value = [default_value] + super(IPOptionsListField, self).__init__( + name, default_value, size=lambda value: len(value) * 8 + ) + + def from_internal(self, raw_value): + return [ + ipoptions_mapping.get(raw_option[0], IPOption)(raw_option) + for raw_option in raw_value + ] + + def to_internal(self, new_value): + if new_value is None or not new_value: + return [] + if not isinstance(new_value, list): + new_value = [new_value] + return [bytes(option) for option in new_value] + + def validate(self, new_value): + if new_value is None or not new_value: + return True + if not isinstance(new_value, list): + new_value = [new_value] + return all( + isinstance(option, (_IPOption, six.binary_type)) for option in new_value + ) diff --git a/src/bf_pktpy/library/fields/mac_field.py b/src/bf_pktpy/library/fields/mac_field.py new file mode 100644 index 0000000..264c46e --- /dev/null +++ b/src/bf_pktpy/library/fields/mac_field.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python + + +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" MACField """ +import six + +from bf_pktpy.library.helpers.mac import correct_mac +from bf_pktpy.library.fields import Field +from scapy_helper import mac2int, int2mac + + +# noinspection PyPropertyAccess,PyAttributeOutsideInit +class MACField(Field): + def __init__(self, name, default_value): + super(MACField, self).__init__(name, default_value, size=48) + + def defaultvalue2mac(self): + return self.from_internal(self._default_value) + + def from_internal(self, raw_value): + return int2mac(raw_value) + + def validate(self, new_value): + try: + if isinstance(new_value, (six.string_types, six.binary_type)): + new_value = correct_mac(new_value) + _ = mac2int(new_value) + return True + if isinstance(new_value, six.integer_types): + if new_value > 2**48 - 1: + return False + _ = int2mac(new_value) + return True + return False + except (TypeError, ValueError): + return False + + def to_internal(self, new_value): + if isinstance(new_value, (six.string_types, six.binary_type)): + new_value = correct_mac(new_value) + return mac2int(new_value) + return int(new_value) diff --git a/src/bf_pktpy/library/fields/short_enum_field.py b/src/bf_pktpy/library/fields/short_enum_field.py new file mode 100644 index 0000000..8542c6e --- /dev/null +++ b/src/bf_pktpy/library/fields/short_enum_field.py @@ -0,0 +1,6 @@ +from bf_pktpy.library.fields import EnumField + + +class ShortEnumField(EnumField): + def __init__(self, name, default_value, types=None): + super(ShortEnumField, self).__init__(name, default_value, size=16, types=types) diff --git a/src/bf_pktpy/library/fields/short_field.py b/src/bf_pktpy/library/fields/short_field.py new file mode 100644 index 0000000..5334b10 --- /dev/null +++ b/src/bf_pktpy/library/fields/short_field.py @@ -0,0 +1,6 @@ +from bf_pktpy.library.fields.bit_field import BitField + + +class ShortField(BitField): + def __init__(self, name, default_value): + super(ShortField, self).__init__(name, default_value, size=16) diff --git a/src/bf_pktpy/library/fields/source_ip_field.py b/src/bf_pktpy/library/fields/source_ip_field.py new file mode 100644 index 0000000..4970417 --- /dev/null +++ b/src/bf_pktpy/library/fields/source_ip_field.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python + + +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" SourceIPField """ +from bf_pktpy.library.helpers.ip import get_src_ip_addr +from bf_pktpy.library.fields.ip_field import IPField + + +# noinspection PyPropertyAccess +class SourceIPField(IPField): + def __init__(self, name, dst_field_name=None): + super(SourceIPField, self).__init__(name, None) + self.dst_field_name = dst_field_name + + def post_build(self, pkt): + if pkt.src is not None: + return + dst_ip = ( + "0.0.0.0" + if self.dst_field_name is None + else getattr(pkt, self.dst_field_name) or "0.0.0.0" + ) + try: + src_ip = get_src_ip_addr(dst_ip) + except RuntimeError as ex: + print("WARNING: %s" % ex) + src_ip = "127.0.0.1" + setattr(pkt, self.name, src_ip) diff --git a/src/bf_pktpy/library/fields/source_mac_field.py b/src/bf_pktpy/library/fields/source_mac_field.py new file mode 100644 index 0000000..6d8b335 --- /dev/null +++ b/src/bf_pktpy/library/fields/source_mac_field.py @@ -0,0 +1,9 @@ +from bf_pktpy.library.helpers.mac import get_src_mac_address +from bf_pktpy.library.fields.mac_field import MACField + + +class SourceMACField(MACField): + def __init__(self, name, default_value=None): + if default_value is None: + default_value = get_src_mac_address() + super(SourceMACField, self).__init__(name, default_value) diff --git a/src/bf_pktpy/library/fields/str_field.py b/src/bf_pktpy/library/fields/str_field.py new file mode 100644 index 0000000..70d6cf8 --- /dev/null +++ b/src/bf_pktpy/library/fields/str_field.py @@ -0,0 +1,57 @@ +import binascii +import math +import six + +from bf_pktpy.library.fields import Field + + +# noinspection PyPropertyAccess,PyAttributeOutsideInit +class StrField(Field): + def __init__(self, name, default_value): + super(StrField, self).__init__( + name, default_value, size=lambda value: len(value) * 8 if value else 0 + ) + + @Field.size.setter + def size(self, new_size): + if callable(new_size): + self._size = new_size + return + default_value_len = ( + len(self.default_value) if self._default_value is not None else 0 + ) + if new_size is None: + self._size = default_value_len * 8 + elif isinstance(new_size, six.integer_types): + self._size = ( + default_value_len * 8 if new_size < default_value_len else new_size * 8 + ) + else: + raise TypeError( + "StrField size should be long or int, not: {}".format( + type(new_size).__name__ + ) + ) + + def value2bin(self, value): + return six.ensure_binary(value) + + def value2hex(self, value): + return binascii.hexlify(value) + + def from_internal(self, raw_value): + return raw_value if raw_value else b"" + + def validate(self, new_value): + return super(StrField, self).validate(new_value) + + def to_internal(self, new_value): + if not new_value: + return b"" + if isinstance(new_value, six.integer_types): + val_size = self._get_size_of_int_val(new_value) + return new_value.to_bytes(val_size, byteorder="big") + return self.value2bin(new_value) + + def _get_size_of_int_val(self, new_value): + return math.ceil(math.log(new_value, 256)) if new_value > 1 else 1 diff --git a/src/bf_pktpy/library/fields/three_bytes_field.py b/src/bf_pktpy/library/fields/three_bytes_field.py new file mode 100644 index 0000000..71b03f1 --- /dev/null +++ b/src/bf_pktpy/library/fields/three_bytes_field.py @@ -0,0 +1,6 @@ +from bf_pktpy.library.fields.bit_field import BitField + + +class ThreeBytesField(BitField): + def __init__(self, name, default_value): + super(ThreeBytesField, self).__init__(name, default_value, size=24) diff --git a/src/bf_pktpy/library/fields/x_3byte_field.py b/src/bf_pktpy/library/fields/x_3byte_field.py new file mode 100644 index 0000000..95003ba --- /dev/null +++ b/src/bf_pktpy/library/fields/x_3byte_field.py @@ -0,0 +1,6 @@ +from bf_pktpy.library.fields.x_bit_field import XBitField + + +class X3ByteField(XBitField): + def __init__(self, name, default_value): + super(X3ByteField, self).__init__(name, default_value, size=24) diff --git a/src/bf_pktpy/library/fields/x_bit_field.py b/src/bf_pktpy/library/fields/x_bit_field.py new file mode 100644 index 0000000..b1bc0ad --- /dev/null +++ b/src/bf_pktpy/library/fields/x_bit_field.py @@ -0,0 +1,17 @@ +from bf_pktpy.library.fields.bit_field import BitField + + +# noinspection PyPropertyAccess +class XBitField(BitField): + def __init__(self, name, default_value, size=1): + super(XBitField, self).__init__(name, default_value, size) + + def __repr__(self): + return "{}(name={}, default_value={}, length={})".format( + type(self).__name__, + self.name, + self.default_value + if self.default_value is None + else hex(self.default_value), + self.size, + ) diff --git a/src/bf_pktpy/library/fields/x_byte_field.py b/src/bf_pktpy/library/fields/x_byte_field.py new file mode 100644 index 0000000..a19e090 --- /dev/null +++ b/src/bf_pktpy/library/fields/x_byte_field.py @@ -0,0 +1,6 @@ +from bf_pktpy.library.fields.x_bit_field import XBitField + + +class XByteField(XBitField): + def __init__(self, name, default_value): + super(XByteField, self).__init__(name, default_value, size=8) diff --git a/src/bf_pktpy/library/fields/x_int_field.py b/src/bf_pktpy/library/fields/x_int_field.py new file mode 100644 index 0000000..33b9c95 --- /dev/null +++ b/src/bf_pktpy/library/fields/x_int_field.py @@ -0,0 +1,6 @@ +from bf_pktpy.library.fields.x_bit_field import XBitField + + +class XIntField(XBitField): + def __init__(self, name, default_value): + super(XIntField, self).__init__(name, default_value, size=32) diff --git a/src/bf_pktpy/library/fields/x_long_field.py b/src/bf_pktpy/library/fields/x_long_field.py new file mode 100644 index 0000000..0e9f08b --- /dev/null +++ b/src/bf_pktpy/library/fields/x_long_field.py @@ -0,0 +1,6 @@ +from bf_pktpy.library.fields.x_bit_field import XBitField + + +class XLongField(XBitField): + def __init__(self, name, default_value): + super(XLongField, self).__init__(name, default_value, size=64) diff --git a/src/bf_pktpy/library/fields/x_short_enum_field.py b/src/bf_pktpy/library/fields/x_short_enum_field.py new file mode 100644 index 0000000..745d669 --- /dev/null +++ b/src/bf_pktpy/library/fields/x_short_enum_field.py @@ -0,0 +1,6 @@ +from bf_pktpy.library.fields import EnumField + + +class XShortEnumField(EnumField): + def __init__(self, name, default_value, types=None): + super(XShortEnumField, self).__init__(name, default_value, size=16, types=types) diff --git a/src/bf_pktpy/library/fields/x_short_field.py b/src/bf_pktpy/library/fields/x_short_field.py new file mode 100644 index 0000000..99921f2 --- /dev/null +++ b/src/bf_pktpy/library/fields/x_short_field.py @@ -0,0 +1,6 @@ +from bf_pktpy.library.fields.x_bit_field import XBitField + + +class XShortField(XBitField): + def __init__(self, name, default_value): + super(XShortField, self).__init__(name, default_value, size=16) diff --git a/src/bf_pktpy/library/helpers/__init__.py b/src/bf_pktpy/library/helpers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/bf_pktpy/library/helpers/bin.py b/src/bf_pktpy/library/helpers/bin.py new file mode 100644 index 0000000..7f27a18 --- /dev/null +++ b/src/bf_pktpy/library/helpers/bin.py @@ -0,0 +1,50 @@ +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import ipaddress +import six + + +def to_bin(value, bit_size=1): + """Convert to binary""" + if isinstance(value, (list, tuple)): + if not value: + return "" + binary = "" + for entry in value: + binary += to_bin(entry) + return binary + + if isinstance(value, six.binary_type): + if not value: + return "0".zfill(bit_size) + return "".join(bin(b)[2:].zfill(8) for b in value) + + if isinstance(value, six.string_types): + if not value: + return "0".zfill(bit_size) + try: + ip = ipaddress.ip_address(six.u(value)) + if ip.version == 4: + return bin(int(ip))[2:].zfill(32) + elif ip.version == 6: + return bin(int(ip))[2:].zfill(128) + except ValueError: + pass + temp = ( + format(i, "b").zfill(bit_size) for i in bytearray(six.ensure_binary(value)) + ) + return "".join(temp) + if bit_size: + return bin(value)[2:].zfill(bit_size) + return bin(value)[2:] diff --git a/src/bf_pktpy/library/helpers/bytes2hex.py b/src/bf_pktpy/library/helpers/bytes2hex.py new file mode 100644 index 0000000..495e935 --- /dev/null +++ b/src/bf_pktpy/library/helpers/bytes2hex.py @@ -0,0 +1,28 @@ +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +def bytes2hex(packet): + import sys + + if sys.version_info.major == 2: + import binascii + + str_hex = binascii.b2a_hex(bytes(packet)) + else: + try: + # noinspection PyUnresolvedReferences + str_hex = bytes(packet).hex() + except TypeError: + # noinspection PyUnresolvedReferences,PyArgumentList + str_hex = bytes(packet, encoding="utf-8").hex() + return str_hex diff --git a/src/bf_pktpy/library/helpers/chksum.py b/src/bf_pktpy/library/helpers/chksum.py new file mode 100644 index 0000000..4d09bf7 --- /dev/null +++ b/src/bf_pktpy/library/helpers/chksum.py @@ -0,0 +1,29 @@ +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +def checksum(binary): + """Calculate checksum""" + prev, total = "", 0 + for i in range(len(binary)): + k = i * 16 + if k >= len(binary): + break + if not prev: + prev = binary[k : k + 16] + continue + now = binary[k : k + 16] + total = int(prev, 2) + int(now, 2) + if total >> 16: + total = (total >> 16) + (total & 0xFFFF) + prev = bin(total) + return total ^ 0xFFFF diff --git a/src/bf_pktpy/library/helpers/constants.py b/src/bf_pktpy/library/helpers/constants.py new file mode 100644 index 0000000..2e27ce2 --- /dev/null +++ b/src/bf_pktpy/library/helpers/constants.py @@ -0,0 +1,21 @@ +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +logo = """ + _______ _______ _______ ___ _ _______ _______ __ __ + | _ || | | || | | || || || | | | + | |_| || ___| | _ || |_| ||_ _|| _ || |_| | + | || |___ | |_| || _| | | | |_| || | + | _ | | ___| | ___|| |_ | | | ___||_ _| + | |_| || | _____ | | | _ | | | | | | | + |_______||___| |_____| |___| |___| |_| |___| |___| |___| +""" diff --git a/src/bf_pktpy/library/helpers/ether_types.py b/src/bf_pktpy/library/helpers/ether_types.py new file mode 100644 index 0000000..1db51a4 --- /dev/null +++ b/src/bf_pktpy/library/helpers/ether_types.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python + + +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### + +ETYPES = { + "0800": "IPv4", + "0806": "ARP", + "8035": "RARP", + "8100": "Dot1Q", + "86dd": "IPv6", + "8847": "MPLS Ucast", + "8848": "MPLS Mcast", + "88a8": "Dot1AD", + "88CC": "LLDP", + "8914": "FCoE", + "9000": "FabricHeader", + "9100": "Vlan Double tag", + "BF01": "SimpleL3SwitchCpuHeader", +} diff --git a/src/bf_pktpy/library/helpers/get_if_list.py b/src/bf_pktpy/library/helpers/get_if_list.py new file mode 100644 index 0000000..82c058d --- /dev/null +++ b/src/bf_pktpy/library/helpers/get_if_list.py @@ -0,0 +1,5 @@ +import psutil + + +def get_if_list(): + return sorted(psutil.net_if_addrs().keys()) diff --git a/src/bf_pktpy/library/helpers/ip.py b/src/bf_pktpy/library/helpers/ip.py new file mode 100644 index 0000000..713faeb --- /dev/null +++ b/src/bf_pktpy/library/helpers/ip.py @@ -0,0 +1,80 @@ +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" IP helpers """ +import platform +import re +import six +import subprocess + +IP_REGEX = ( + r"(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.)" + r"{3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)" +) + + +def _get_src_ip_addr_system_independent(cmd, output_regex): + try: + process = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE) + output, error = process.communicate() + ret_code = process.returncode + except (OSError, ValueError, subprocess.CalledProcessError) as ex: + raise OSError(ex) + if error: + raise OSError(ret_code, error) + + try: + return next(match for match in re.findall(output_regex, six.ensure_str(output))) + except StopIteration: + raise RuntimeError("Could not get egress IP from OS") + + +def get_src_ip_addr(dst_ip): + """Retrieves egress IP for given destination IP address + + Supported OS: Windows, Linux + + :param dst_ip: destination IP address + :type dst_ip: basestring + :return: egress IP address + :rtype: str + :raise TypeError: when provided destination IP is not of string type + :raise ValueError: when provided destination IP is invalid + :raise OSError: when running subprocess command exits with non-zero status + :raise RuntimeError: when function could not get egress IP for other reason than in other defined exceptions; or when run on not supported OS + """ + if not isinstance(dst_ip, six.string_types): + raise TypeError("Provided dst IP %s is not of string type" % dst_ip) + if not re.match(IP_REGEX, dst_ip): + raise ValueError("Provided dst IP %s is invalid" % dst_ip) + + system = platform.system() + if not system: + import os + + system = os.name + + if system in ("Windows", "nt"): + cmd = "pathping -n -w 1 -h 1 -q 1 %s" % dst_ip + output_regex = r"0\s*(?P" + IP_REGEX + ")" + return _get_src_ip_addr_system_independent(cmd, output_regex) + + if system in ("Linux", "posix"): + cmd = "ip r g %s" % dst_ip + output_regex = r"dev\s+\w+\s+src\s+(?P" + IP_REGEX + ")" + return _get_src_ip_addr_system_independent(cmd, output_regex) + + raise RuntimeError( + "Could not determine system or system is not supported " + "(currently supported systems: Linux, Windows)" + ) diff --git a/src/bf_pktpy/library/helpers/ip_types.py b/src/bf_pktpy/library/helpers/ip_types.py new file mode 100644 index 0000000..be5f834 --- /dev/null +++ b/src/bf_pktpy/library/helpers/ip_types.py @@ -0,0 +1,146 @@ +ITYPES = { + 0x00: "HOPOPT", + 0x01: "ICMP", + 0x02: "IGMP", + 0x03: "GGP", + 0x04: "IP-in-IP", + 0x05: "ST", + 0x06: "TCP", + 0x07: "CBT", + 0x08: "EGP", + 0x09: "IGP", + 0x0A: "BBN-RCC-MON", + 0x0B: "NVP-II", + 0x0C: "PUP", + 0x0D: "ARGUS", + 0x0E: "EMCON", + 0x0F: "XNET", + 0x10: "CHAOS", + 0x11: "UDP", + 0x12: "MUX", + 0x13: "DCN-MEAS", + 0x14: "HMP", + 0x15: "PRM", + 0x16: "XNS-IDP", + 0x17: "TRUNK-1", + 0x18: "TRUNK-2", + 0x19: "LEAF-1", + 0x1A: "LEAF-2", + 0x1B: "RDP", + 0x1C: "IRTP", + 0x1D: "ISO-TP4", + 0x1E: "NETBLT", + 0x1F: "MFE-NSP", + 0x20: "MERIT-INP", + 0x21: "DCCP", + 0x22: "3PC", + 0x23: "IDPR", + 0x24: "XTP", + 0x25: "DDP", + 0x26: "IDPR-CMTP", + 0x27: "TP++", + 0x28: "IL", + 0x29: "IPv6", + 0x2A: "SDRP", + 0x2B: "IPv6-Route", + 0x2C: "IPv6-Frag", + 0x2D: "IDRP", + 0x2E: "RSVP", + 0x2F: "GRE", + 0x30: "DSR", + 0x31: "BNA", + 0x32: "ESP", + 0x33: "AH", + 0x34: "I-NLSP", + 0x35: "SwIPe", + 0x36: "NARP", + 0x37: "MOBILE", + 0x38: "TLSP", + 0x39: "SKIP", + 0x3A: "IPv6-ICMP", + 0x3B: "IPv6-NoNxt", + 0x3C: "IPv6-Opts", + 0x3D: "Any host internal protocol", + 0x3E: "CFTP", + 0x3F: "Any local network", + 0x40: "SAT-EXPAK", + 0x41: "KRYPTOLAN", + 0x42: "RVD", + 0x43: "IPPC", + 0x44: "Any distributed file system", + 0x45: "SAT-MON", + 0x46: "VISA", + 0x47: "IPCU", + 0x48: "CPNX", + 0x49: "CPHB", + 0x4A: "WSN", + 0x4B: "PVP", + 0x4C: "BR-SAT-MON", + 0x4D: "SUN-ND", + 0x4E: "WB-MON", + 0x4F: "WB-EXPAK", + 0x50: "ISO-IP", + 0x51: "VMTP", + 0x52: "SECURE-VMTP", + 0x53: "VINES", + 0x54: "TTP", + 0x55: "NSFNET-IGP", + 0x56: "DGP", + 0x57: "TCF", + 0x58: "EIGRP", + 0x59: "OSPF", + 0x5A: "Sprite-RPC", + 0x5B: "LARP", + 0x5C: "MTP", + 0x5D: "AX.25", + 0x5E: "OS", + 0x5F: "MICP", + 0x60: "SCC-SP", + 0x61: "ETHERIP", + 0x62: "ENCAP", + 0x63: "Any private encryption scheme", + 0x64: "GMTP", + 0x65: "IFMP", + 0x66: "PNNI", + 0x67: "PIM", + 0x68: "ARIS", + 0x69: "SCPS", + 0x6A: "QNX", + 0x6B: "A/N", + 0x6C: "IPComp", + 0x6D: "SNP", + 0x6E: "Compaq-Peer", + 0x6F: "IPX-in-IP", + 0x70: "VRRP", + 0x71: "PGM", + 0x72: "Any 0-hop protocol", + 0x73: "L2TP", + 0x74: "DDX", + 0x75: "IATP", + 0x76: "STP", + 0x77: "SRP", + 0x78: "UTI", + 0x79: "SMP", + 0x7A: "SM", + 0x7B: "PTP", + 0x7C: "IS-IS", + 0x7D: "FIRE", + 0x7E: "CRTP", + 0x7F: "CRUDP", + 0x80: "SSCOPMCE", + 0x81: "IPLT", + 0x82: "SPS", + 0x83: "PIPE", + 0x84: "SCTP", + 0x85: "FC", + 0x86: "RSVP-E2E-IGNORE", + 0x87: "Mobility Header", + 0x88: "UDPLite", + 0x89: "MPLS-in-IP", + 0x8A: "manet", + 0x8B: "HIP", + 0x8C: "Shim6", + 0x8D: "WESP", + 0x8E: "ROHC", + 0x8F: "Ethernet", +} diff --git a/src/bf_pktpy/library/helpers/mac.py b/src/bf_pktpy/library/helpers/mac.py new file mode 100644 index 0000000..28f3509 --- /dev/null +++ b/src/bf_pktpy/library/helpers/mac.py @@ -0,0 +1,74 @@ +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import binascii +import getmac +import six + +from scapy_helper import int2mac + + +def get_src_mac_address(): + """ + Get MAC Address of the host + """ + # noinspection PyBroadException + try: + mac = getmac.get_mac_address() + if mac is None: + return "00:00:00:00:00:00" + return mac + except Exception: + return "00:00:00:00:00:00" + + +def correct_mac(value): + if isinstance(value, six.integer_types): + return int2mac(value) + + if isinstance(value, six.binary_type): + if b"-" in value: + value = value.replace(b"-", b"") + if b"0x" in value: + value = value.replace(b"0x", b"") + if b":" in value: + value = b"".join(x.zfill(2) for x in value.split(b":")) + try: + str_value = six.ensure_str(value) + int(str_value, 16) + except ValueError: + str_value = six.ensure_str(binascii.hexlify(value)) + return ":".join( + str_value[i : i + 2].zfill(2) for i in range(0, len(str_value), 2) + ) + + if isinstance(value, six.string_types): + value = six.ensure_str(value) + if "-" in value: + value = value.replace("-", "") + if "0x" in value: + value = value.replace("0x", "") + if ":" in value: + value = "".join(x.zfill(2) for x in value.split(":")) + # case when provide address as encoded string: SEEYOU -> 53:45:45:59:4f:55 (hex) + if len(value) == 6: + value = "".join("%02x" % ord(num) for num in value) + try: + int(value, 16) + except ValueError: + value = six.ensure_str(binascii.hexlify(six.ensure_binary(value))) + return ":".join(value[i : i + 2].zfill(2) for i in range(0, len(value), 2)) + + raise TypeError( + "Only values of type: %s are supported" % six.integer_types + + six.string_types + + (six.binary_type,) + ) diff --git a/src/bf_pktpy/library/specs/__init__.py b/src/bf_pktpy/library/specs/__init__.py new file mode 100644 index 0000000..4907668 --- /dev/null +++ b/src/bf_pktpy/library/specs/__init__.py @@ -0,0 +1,25 @@ +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from bf_pktpy.library.specs.ethernet import Ether +from bf_pktpy.library.specs.ipv4 import IP +from bf_pktpy.library.specs.templates.ipv6 import IPv6 +from bf_pktpy.library.specs.templates.arp import ARP +from bf_pktpy.library.specs.tcp import TCP +from bf_pktpy.library.specs.udp import UDP +from bf_pktpy.library.specs.icmp import ICMP +from bf_pktpy.library.specs.gre import GRE +from bf_pktpy.library.specs.bfd import BFD +from bf_pktpy.library.specs.bootp import BOOTP +from bf_pktpy.library.specs.dhcp import DHCP +from bf_pktpy.library.specs.dot1q import Dot1Q +from bf_pktpy.library.specs.templates.mpls import MPLS diff --git a/src/bf_pktpy/library/specs/base.py b/src/bf_pktpy/library/specs/base.py new file mode 100644 index 0000000..dab5703 --- /dev/null +++ b/src/bf_pktpy/library/specs/base.py @@ -0,0 +1,640 @@ +#!/usr/bin/env python + + +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" Base class """ +import abc +import binascii +from collections import OrderedDict +import copy +from functools import wraps +import pprint +import six + +from bf_pktpy.library.helpers.bin import to_bin +from bf_pktpy.library.specs.pretty import pretty, docnote, footnote, todict + +# ============================================================================= + +_INSTANCES = {} + + +def singleton(cls): + """Singleton""" + + @wraps(cls) + def get_instance(*args, **kwargs): + instance = _INSTANCES.get(cls, None) + if not instance: + instance = cls(*args, **kwargs) + _INSTANCES[cls] = instance + return instance + + return get_instance + + +def get_instance(child, parent, case=None): + """Get acceptable value""" + if child == case: + return child + if isinstance(child, dict): + child = parent(**child) + if not isinstance(child, parent): + raise ValueError("Value must be an instance of " + parent.__name__) + return child + + +def set_instance(this, value): + """Set acceptable value""" + if isinstance(value, dict): + for key, val in value.items(): + if hasattr(this, key): + setattr(this, key, val) + else: + raise ValueError("Unsupported value :", value) + + +class Base: + """Base: foundation base class to inheritance""" + + _ETYPES = { + "0800": "IPv4", + "86dd": "IPv6", + "8100": "Dot1Q", + "0806": "ARP", + "8035": "RARP", + "8847": "MPLS Ucast", + "8848": "MPLS Mcast", + "88a8": "Dot1AD", + "88CC": "LLDP", + "9100": "Vlan Double tag", + "8914": "FCoE", + } + name = "Base" + + def __init__(self, **kwargs): + self._body = None + self.sniffed_on = None # interface name + self._member_mask = None + self.underlayer = None + + self._lock = {} # lock mechanism + + def recalculate_hex(self): + """ + Force a recalculation of hex + :return: None + """ + self.hex() + + @abc.abstractmethod + def _members(self): + return + + def __repr__(self): + member_key = [x for x in self.members.keys()][0] + payload = [] + if self._body is not None: + payload.append(repr(self._body)) + return "<{} {} |{}>".format( + self.name, + " ".join(["{}={}".format(x[0], x[1]) for x in self.members[member_key]]), + " ".join(payload), + ) + + def __index__(self): + return 0 + + @staticmethod + def _prepare_kwargs(kwargs): + for k, v in six.iteritems(kwargs.copy()): + if v is None: + del kwargs[k] + if isinstance(v, six.string_types): + kwargs[k] = six.ensure_str(v) + + def _all_members(self): + members = self.members + inner = self.body + while inner: + if not isinstance(inner, Base): + members.update({"packet_payload": inner}) + break + name = next(member_name for member_name in inner.members.keys()) + if name in members: + ct = sum([1 for each in members if name + "_" in each]) + new_name = name + "_" + str(ct + 1) + new_inner = {new_name: inner.members.pop(name)} + members.update(new_inner) + else: + members.update(inner.members) + if not inner.body: + break + inner = inner.body + return members + + def is_lock(self, key, default_value=None): + return self._lock.get(key, default_value) + + def lock(self, key, value=True): + self._lock[key] = value + + def lock_all(self): + for k, _ in self._lock.items(): + self._lock[k] = True + + def unlock_all(self): + for k, _ in self._lock.items(): + self._lock[k] = False + + def show(self): + members = self._all_members() + return pprint.pformat(todict(members), indent=4) + + def setfieldval(self, field_name, new_val): + if not hasattr(self, field_name): + raise ValueError("%s is not a field of %s" % (field_name, self.name)) + setattr(self, field_name, new_val) + + def __contains__(self, item): + return self.get(item) is not False + + def __getitem__(self, key): + # TODO This is hack for temporary work with bf-switch tests + if key == "Ethernet": + key = "Ether" + current = self + + if isinstance(key, int): + current = self.getlayer(key) + if current is None: + raise IndexError("Layer [%s] not found" % key) + return current + + if isinstance(key, slice): + name = key.start.__name__ + idx = key.stop + count = 1 + while count <= idx: + if current.name == name: + if count == idx: + return current + count += 1 + current = current.body + if not hasattr(current, "name"): + break + raise IndexError("Could not find header of type '%s' in frame" % key) + + if isinstance(key, type) and issubclass(key, Base): + name = key.name + elif isinstance(key, str): + name = key + else: + name = key.__name__ + + while current is not None: + if current.name == name: + return current + current = current._body + if not hasattr(current, "name"): + break + raise IndexError("Could not find header of type '%s' in frame" % key) + + def __str__(self): + """ptf's bytes compatibility""" + if six.PY2: + return self.pack() + return str(self.pack()) + + def __bytes__(self): + return self.pack() + + def __call__(self): + return todict(self.members) + + def __div__(self, other): + return self.__truediv__(other) + + def __truediv__(self, body): + if isinstance(body, (Base, six.binary_type, six.string_types)): + result = self.copy() + body_copy = body.copy() if hasattr(body, "copy") else body + result._add_layer(body_copy) + result.post_build() + return result + else: + raise ValueError("Unsupported value type: %s" % type(body)) + + def __len__(self): + """ptf's len compatibility""" + return len(self.bin()) // 8 + + def _add_layer(self, body_copy): + if self.body is not None: + self.body._add_layer(body_copy) + else: + if isinstance(body_copy, (six.binary_type, six.string_types)): + self._body = self._create_raw_packet(six.ensure_binary(body_copy)) + if self.name == "IPv6ExtHdrRouting": + self.nh = 59 + elif body_copy.__class__.__name__ == "Raw": + self._body = body_copy + else: + self._combine(body_copy) + + if self.body is not None: + self.body.underlayer = self + + def _create_raw_packet(self, str_payload): + import importlib + + mod = importlib.import_module("bf_pktpy.library.specs.templates.raw") + raw_pkt_class = getattr(mod, "Raw") + return raw_pkt_class(load=str_payload) + + def _combine(self, body_copy): + """ + Method to overload by all child headers if specific bindings need to be + defined + """ + self._body = body_copy + return self + + @staticmethod + def calculate_hdr(members): + """ + Calculate the header size, base on the information + stored in the members + """ + if members is None: + return 0 + for _, member in members.items(): + return int(sum([field[2] for field in member]) / 8) + + @property + def hdr_len(self): + """ + Return information about header size, base on information + stored at the members method + """ + return self.calculate_hdr(self._members()) + + @property + def total_len(self): + """ + Return a total length of frame from position on which + is called to the end (all packets on the right) + """ + if self._body is None: + return self.hdr_len + if isinstance(self._body, (six.binary_type, six.string_types)): + return self.hdr_len + len(self._body) + if hasattr(self._body, "total_len"): + return self.hdr_len + self._body.total_len + return self.hdr_len + + @property + def payload(self): + """ + Return a payload -- _body + """ + return self._body + + @payload.setter + def payload(self, value): + """ + Try to set new value for payload + """ + self._body = value + + @payload.deleter + def payload(self): + """ + Remove payload + """ + self._body = None + + def remove_payload(self): + self._body = None + + def get(self, layer, return_value=False): + try: + return self.__getitem__(layer) + except IndexError: + return return_value + + def get_last(self): + """Return the last protocol on the stack""" + current = self + while current.body is not None and hasattr(current.body, "name"): + current = current.body + return current + + def getlayer(self, layer_number): + if layer_number < 0: + return None + + current = self + i = 0 + while ( + current.body is not None + and hasattr(current.body, "name") + and layer_number > i + ): + i += 1 + current = current.body + + if layer_number > i: + return None + return current + + def haslayer(self, layer_name): + if not isinstance(layer_name, str): + layer_name = layer_name.__name__ + + try: + _ = self[layer_name] + return True + except IndexError: + return False + + def args_details(self): + members = self._members() + det = [] + if members is None: + return det + + # members should return only one child + for x in list(members.values())[0]: + det.append((x[0], x[2])) # (name, width) + return det + + def info(self, option="yaml"): + """to yaml or json""" + members = self._all_members() + pretty(members, option) + + def bin(self, header=False, field=None, **fields_to_override): + """To binary + + Args: + header (bool): convert header to bin only + field (str): field name + Returns: + str: string of binary + Examples: + | binary = self.bin() + | + """ + binary = "" + + _, props = tuple(six.iteritems(self._members(**fields_to_override)))[0] + for name, value, size in props: + if field and field == name: + return to_bin(value, size) + if self._member_mask and name not in self._member_mask: + continue + binary += to_bin(value, size) + if not header: + if hasattr(self._body, "bin"): + self._body.post_build() + binary += self._body.bin() + elif ( + isinstance(self._body, (six.binary_type, six.string_types)) + and self._body + ): + encoded_body = six.ensure_binary(self._body) + hex_body = binascii.hexlify(encoded_body) + binary += bin(int(hex_body, 16))[2:].zfill(4 * len(hex_body)) + elif isinstance(self._body, int): + binary += bin(self._body)[2:] + return binary + + def hex(self, field=None, whitespace=True): + """ + To hex + :param field: + :param whitespace: "00 00" if whitespace else "0000" + :return: string of hex + """ + binary = self.bin(field) + offset = 0 + hexa = "" + while offset + 8 <= len(binary): + bits = binary[offset : offset + 8] + hexa += hex(int(bits, 2))[2:].rjust(2, "0") + " " + offset += 8 + if whitespace: + return hexa.rstrip() + return "".join(hexa.rstrip().split()) + + def pack(self): + """Hex to bin""" + hexa = self.hex().replace(" ", "") + return binascii.unhexlify(hexa) + + def build(self): + """alias for "pack" for compatibility with Scapy + + Does exactly the same as `bytes(pkt)` + """ + return self.pack() + + @property + def body(self): + return self._body + + def copy(self): + """ptf's packet.copy() compatibility""" + return copy.deepcopy(self) + + @property + def members(self): + """Get members""" + data = OrderedDict() + _members = self._members() + if _members: + member, props = tuple(_members.items())[0] + temp = OrderedDict() + for each in props: + if len(each) == 3: + key, value, _ = each + if self._member_mask and key not in self._member_mask: + continue + temp.update({key: value}) + continue + if isinstance(each, dict): + for group in tuple(each.items()): + if len(group) == 2: + key, value = group + if self._member_mask and key not in self._member_mask: + continue + temp.update({key: value}) + data[member] = tuple(temp.items()) + return data + + def load_hex(self, value): + """ + Load hex into packet structure. + + pkt = Ether() / IP() / TCP() + new_packet = pkt.load_bytes(bytes(pkt) + :param value: bytes array + :return: New Packet (Base) Object + """ + return self.load_bytes(binascii.unhexlify(value.replace(" ", ""))) + + def load_bytes(self, value): + """ + Load bytes into packet structure. + + pkt = Ether() / IP() / TCP() + new_packet = pkt.load_bytes(bytes(pkt) + :param value: bytes array + :return: New Packet (Base) Object + """ + + def create_structure(): + potential_packet_list = [] + current = self + while current is not None: + potential_packet_list.append( + (current.name, next(x for x in current._members().values())) + ) + current = current._body + if not hasattr(current, "name"): + break + + packets_list = [] + for key_member, value_member in potential_packet_list: + temp_ordered_dict = OrderedDict() + for element in value_member: + # element[0] packet argument, element[2] arg len in B + temp_ordered_dict[element[0]] = element[2] + packets_list.append((key_member, temp_ordered_dict)) + return packets_list + + def bitstring_to_bytes(bit_string): + return int(bit_string, 2).to_bytes(len(bit_string) // 8, byteorder="big") + + def combine(structure): + from importlib import import_module + + last = 0 + packet = None + + for packet_class, members in structure: + if last >= len(value): + break + member_mask = set() + packet_template = import_module("bf_pktpy.packets").__getattribute__( + packet_class + ) + kwargs = {} + for arg_name, arg_len in members.items(): + bin_value = value[last : last + arg_len] + if len(bin_value) != arg_len and packet_class != "Raw": + last = len(value) + break + if packet_class == "Raw": + payload_bit_strs = [ + bin_value[i : i + 8] for i in range(0, len(bin_value), 8) + ] + kwargs[arg_name] = b"".join( + bitstring_to_bytes(bit_str) for bit_str in payload_bit_strs + ) + else: + kwargs[arg_name] = int(value[last : last + arg_len], 2) + member_mask.add(arg_name) + last += arg_len + if not packet: + packet = packet_template(**kwargs) + packet._member_mask = member_mask + continue + part = packet_template(**kwargs) + part._member_mask = member_mask + part.lock_all() + packet = packet / part + if last < len(value): + remainder = value[last:] + payload_bytes = binascii.unhexlify( + "".join( + "%02x" % int(remainder[i : i + 8], 2) + for i in range(0, len(remainder), 8) + ) + ) + packet = packet / payload_bytes + packet.lock_all() + return packet + + if not value: + return self.copy() + + if six.PY2: + value = "".join(format(ord(byte), "08b") for byte in value) + else: + value = "".join(format(byte, "08b") for byte in value) + + return combine(create_structure()) + + @property + def parameters(self): + """Get parameters""" + params = {} + if self._members(): + _, props = tuple(self._members().items())[0] + for each in props: + if len(each) == 3: + key, value, _ = each + if self.name == "Ether": + if key in ("src", "dst"): + mac = hex(value)[2:].rjust(12, "0") + mac = [mac[i : i + 2] for i in range(0, 12, 2)] + value = ":".join(mac) + if self.name == "IP": + if key in ("src", "dst"): + addr = hex(value)[2:].rjust(8, "0") + addr = [ + str(int(addr[x : x + 2], 16)) for x in range(0, 8, 2) + ] + value = ".".join(addr) + params.update({key: value}) + return params + + def post_build(self): + self._post_build() + if self.body is not None and isinstance(self.body, Base): + self.body.post_build() + + def _post_build(self): + """Dummy def for signaling a place for all actions + which need to be done before sending a packet""" + pass + + @classmethod + def help(cls): + """Helper method to provide additional help info""" + docnote(cls.__doc__) + instances, name, lookfor = {}, "", "" + for line in cls.__doc__.split("\n"): + if name in instances and not instances[name]: + if lookfor in line: + for word in line.split(): + if word.endswith(lookfor): + instances[name] = word + footnote(instances) + + +# ============================================================================= diff --git a/src/bf_pktpy/library/specs/bfd.py b/src/bf_pktpy/library/specs/bfd.py new file mode 100644 index 0000000..4ba371b --- /dev/null +++ b/src/bf_pktpy/library/specs/bfd.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python + + +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" BFD class """ +from bf_pktpy.library.specs.container import Container +from bf_pktpy.library.specs.templates import BFDTemplate + + +# ============================================================================= + + +class BFD(Container): + """BFD class""" + + fields = ( + "version diag sta flags detect_mult len my_discriminator " + "your_discriminator min_tx_interval min_rx_interval " + "echo_rx_interval" + ).split() + + def __init__(self, **kwargs): + super(BFD, self).__init__(BFDTemplate, **kwargs) + + +# ============================================================================= diff --git a/src/bf_pktpy/library/specs/bootp.py b/src/bf_pktpy/library/specs/bootp.py new file mode 100644 index 0000000..f9a805a --- /dev/null +++ b/src/bf_pktpy/library/specs/bootp.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python + + +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" BOOTP class """ +from bf_pktpy.library.specs.container import Container +from bf_pktpy.library.specs.templates.bootp import BOOTP as BOOTPTemplate + + +# ============================================================================= +class BOOTP(Container): + """BOOTP class""" + + fields = ( + "op htype hlen hops xid secs flags ciaddr yiaddr siaddr giaddr " + "chaddr sname file options" + ).split() + + def __init__(self, **kwargs): + super(BOOTP, self).__init__(BOOTPTemplate, **kwargs) + + +# ============================================================================= diff --git a/src/bf_pktpy/library/specs/constant.py b/src/bf_pktpy/library/specs/constant.py new file mode 100644 index 0000000..1092d87 --- /dev/null +++ b/src/bf_pktpy/library/specs/constant.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python + + +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" Define constants """ +IP_PATTERN = "^([0-9][0-9]{0,2}\\.){3}[0-9][0-9]{0,2}$" +MASK_PATTERN = "^255\\.255\\.[0-9][0-9]{0,2}\\.0$" +MAC_PATTERN = "^([a-z0-9]{2}:){5}[a-z0-9]{2}$" +PORT_PATTERN = "^[1-9][0-9]*$" + +# ============================================================================= diff --git a/src/bf_pktpy/library/specs/container.py b/src/bf_pktpy/library/specs/container.py new file mode 100644 index 0000000..794c3bb --- /dev/null +++ b/src/bf_pktpy/library/specs/container.py @@ -0,0 +1,61 @@ +class Container(list): + """Container class to contain protocols""" + + fields = [] + count_field_with_values = 0 + + def __init__(self, template, **kwargs): + super(Container, self).__init__() + self.params = [] + self.template = template + + names = [] + for field in self.fields: + if field == "options": + # DHCP uses a list of tuples for "options" values. Skip + continue + values = kwargs.get(field) + if isinstance(values, (tuple, list)): + names.append(field) + + # support at most 1 field with multiple values + if len(names) > 1: + raise ValueError("Only support at most 1 field with multi values") + if len(names) == 1: + Container.count_field_with_values += 1 + name = names[0] + values = kwargs.get(name) + for value in values: + copy = kwargs.copy() + copy.update({name: value}) + self.params.append(copy) + self.append(template(**copy)) + else: + self.params.append(kwargs) + self.append(template(**kwargs)) + + # Check all protocol layers + # if Container.count_field_with_values > 1: + # raise ValueError("Only support at most 1 field with multi values") + + @property + def name(self): + return self.__class__.__name__ + + def clone(self, index): + """Clone to a new object using parameter at index""" + return self.template(**self.params[index]) + + def clear(self): + """Remove all items""" + while self: + self.pop() + + def count_layers(self): + """Return how many protocol layers existed""" + count = 0 + inner = self[0] + while hasattr(inner, "name"): + count += 1 + inner = inner.body + return count diff --git a/src/bf_pktpy/library/specs/dhcp.py b/src/bf_pktpy/library/specs/dhcp.py new file mode 100644 index 0000000..22254ca --- /dev/null +++ b/src/bf_pktpy/library/specs/dhcp.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python + + +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" DHCP class """ +from bf_pktpy.library.specs.container import Container +from bf_pktpy.library.specs.templates.dhcp import DHCP as DHCPTemplate + + +# ============================================================================= +class DHCP(Container): + """DHCP class""" + + fields = ("options",) + + def __init__(self, **kwargs): + super(DHCP, self).__init__(DHCPTemplate, **kwargs) + + +# ============================================================================= diff --git a/src/bf_pktpy/library/specs/dot1q.py b/src/bf_pktpy/library/specs/dot1q.py new file mode 100644 index 0000000..2f8d8a6 --- /dev/null +++ b/src/bf_pktpy/library/specs/dot1q.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python + + +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" Dot1Q class """ +from bf_pktpy.library.specs.container import Container +from bf_pktpy.library.specs.templates.dot1q import Dot1Q as Dot1QTemplate + + +# ============================================================================= +class Dot1Q(Container): + """Dot1Q class""" + + fields = "type prio id vlan".split() + + def __init__(self, **kwargs): + super(Dot1Q, self).__init__(Dot1QTemplate, **kwargs) + + def __truediv__(self, payload): + self.clear() + + if payload.name in ("UDP", "TCP", "ICMP"): + if len(payload) > 1: + if len(self.params) > 1: + raise ValueError("Only support at most 1 dynamic field") + for layer4 in payload: + ipv4 = self.clone(0) + self.append(ipv4 / layer4) + return self + + if len(self.params) > 1: + for idx in range(len(self.params)): + ipv4 = self.clone(idx) + layer4 = payload[0] + self.append(ipv4 / layer4) + return self + + ipv4 = self.clone(0) + self.append(ipv4 / payload[0]) + return self + + +# ============================================================================= diff --git a/src/bf_pktpy/library/specs/ethernet.py b/src/bf_pktpy/library/specs/ethernet.py new file mode 100644 index 0000000..4d7a7e0 --- /dev/null +++ b/src/bf_pktpy/library/specs/ethernet.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python + + +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" Ether class """ +from bf_pktpy.library.specs.container import Container +from bf_pktpy.library.specs.templates import clone +from bf_pktpy.library.specs.templates.ethernet import Ether as EtherTemplate + + +# ============================================================================= +class Ether(Container): + """Ether class""" + + fields = "src dst type".split() + + def __init__(self, **kwargs): + super(Ether, self).__init__(EtherTemplate, **kwargs) + Container.count_field_with_values = 0 + + def clone_parent(self, idx): + """Clone parent""" + cloned_parent = self.clone(idx) + child = self[idx].body + while hasattr(child, "name"): + cloned_child = clone(child) + cloned_parent = cloned_parent / cloned_child + child = child.body + return cloned_parent + + def __div__(self, other): + self.__truediv__(other) + + def __truediv__(self, child_container): + new_list = [] + if len(child_container) > 1: + # Last protocol has a field with multiple values + for child in child_container: + parent = self.clone_parent(0) + new_list.append(parent / child) + self.clear() + self.extend(new_list) + return self + + if len(self.params) > 1: + # First protocol has a field with multiple values + for idx in range(len(self.params)): + parent = self.clone_parent(idx) + child = clone(child_container[0]) + new_list.append(parent / child) + self.clear() + self.extend(new_list) + return self + + if len(self) > 1: + # Existing protocol(s) has a field with multiple values + for parent in self: + child = clone(child_container[0]) + parent / child + return self + + # no protocols have multiple values + parent = self.clone_parent(0) + temp = parent / child_container[0] + new_list.append(temp) + self.clear() + self.extend(new_list) + return self + + +# ============================================================================= diff --git a/src/bf_pktpy/library/specs/extends/__init__.py b/src/bf_pktpy/library/specs/extends/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/bf_pktpy/library/specs/extends/l4checksum.py b/src/bf_pktpy/library/specs/extends/l4checksum.py new file mode 100644 index 0000000..eed7080 --- /dev/null +++ b/src/bf_pktpy/library/specs/extends/l4checksum.py @@ -0,0 +1,33 @@ +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from bf_pktpy.library.helpers.chksum import checksum + + +class L4Checksum: + def __init__(self): + self._body = None + + def l4_checksum(self): + """Calculate checksum""" + if self._body is None: + return + + if not self._body.is_lock("chksum", False): + if hasattr(self._body, "_chksum"): + self._body._chksum = 0 + else: + self._body.chksum = 0 + binary = self._body.bin() + if len(binary) % 16 > 0: + binary += "00000000" + return checksum(binary) diff --git a/src/bf_pktpy/library/specs/gre.py b/src/bf_pktpy/library/specs/gre.py new file mode 100644 index 0000000..38452a8 --- /dev/null +++ b/src/bf_pktpy/library/specs/gre.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python + + +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" GRE class """ +from bf_pktpy.library.specs.container import Container +from bf_pktpy.library.specs.templates.gre import GRE as GRETemplate + + +# ============================================================================= +class GRE(Container): + """GRE class""" + + fields = ( + "chksum_present routing_present key_present seqnum_present " + "strict_route_source recursion_control flags version proto " + "chksum offset key sequence_number" + ).split() + + def __init__(self, **kwargs): + super(GRE, self).__init__(GRETemplate, **kwargs) + + def __truediv__(self, payload): + self.clear() # not done + + return self + + +# ============================================================================= diff --git a/src/bf_pktpy/library/specs/icmp.py b/src/bf_pktpy/library/specs/icmp.py new file mode 100644 index 0000000..31e9378 --- /dev/null +++ b/src/bf_pktpy/library/specs/icmp.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python + + +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" ICMP class """ +from bf_pktpy.library.specs.container import Container +from bf_pktpy.library.specs.templates.icmp import ICMP as ICMPTemplate + + +# ============================================================================= +class ICMP(Container): + """ICMP class""" + + fields = "type code chksum id seq".split() + + def __init__(self, **kwargs): + super(ICMP, self).__init__(ICMPTemplate, **kwargs) + + def __truediv__(self, payload): + self.clear() # not done + return self + + +# ============================================================================= diff --git a/src/bf_pktpy/library/specs/ipv4.py b/src/bf_pktpy/library/specs/ipv4.py new file mode 100644 index 0000000..5bc37c7 --- /dev/null +++ b/src/bf_pktpy/library/specs/ipv4.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python + + +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" IP class """ +from bf_pktpy.library.specs.container import Container +from bf_pktpy.library.specs.templates.ipv4 import IP as IPv4Template + + +# ============================================================================= +class IP(Container): + """IP class""" + + fields = ( + "version ihl tos len id flags frag ttl proto chksum src dst " "options" + ).split() + + def __init__(self, **kwargs): + super(IP, self).__init__(IPv4Template, **kwargs) + + def __truediv__(self, child_container): + self.clear() + + if child_container.name in ("UDP", "TCP", "ICMP"): + if len(child_container) > 1: + for child in child_container: + parent = self.clone(0) + self.append(parent / child) + return self + + if len(self.params) > 1: + for idx in range(len(self.params)): + parent = self.clone(idx) + child = child_container[0] + self.append(parent / child) + return self + + parent = self.clone(0) + self.append(parent / child_container[0]) + return self + + +# ============================================================================= diff --git a/src/bf_pktpy/library/specs/ipv6.py b/src/bf_pktpy/library/specs/ipv6.py new file mode 100644 index 0000000..39b7d04 --- /dev/null +++ b/src/bf_pktpy/library/specs/ipv6.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python + + +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" IPv6 class """ +from bf_pktpy.library.specs.container import Container +from bf_pktpy.library.specs.templates.ipv6 import IPv6 as IPv6Template + + +# ============================================================================= +class IPv6(Container): + """IPv6 class""" + + fields = ( + "version ihl tos len id flags frag ttl proto chksum src dst " "options" + ).split() + + def __init__(self, **kwargs): + super(IPv6, self).__init__(IPv6Template, **kwargs) + + def __truediv__(self, child_container): + self.clear() + + if child_container.name in ("UDP", "TCP", "ICMP"): + if len(child_container) > 1: + for child in child_container: + parent = self.clone(0) + self.append(parent / child) + return self + + if len(self.params) > 1: + for idx in range(len(self.params)): + parent = self.clone(idx) + child = child_container[0] + self.append(parent / child) + return self + + parent = self.clone(0) + self.append(parent / child_container[0]) + return self + + +# ============================================================================= diff --git a/src/bf_pktpy/library/specs/packet.py b/src/bf_pktpy/library/specs/packet.py new file mode 100644 index 0000000..91d75e1 --- /dev/null +++ b/src/bf_pktpy/library/specs/packet.py @@ -0,0 +1,239 @@ +#!/usr/bin/env python + + +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" Packet class """ +import six +import warnings + +from bf_pktpy.library.specs.base import Base + + +class Packet(Base): + """Packet class which holds validation fields logic. + + In order to use it one needs to define headers like this: + + class MyPacket(Packet): + name = "MyCustomPacket"\n + fields_desc = [ + MyField("test", 0, size=24),\n + StrField("foo", "bar") + ] + + Then, Packet will take care of creating fields according to this definition, handle + value transformations (external <-> internal) and validation (defined in the + field class). + """ + + name = "" + fields_desc = [] + + # TODO(sborkows): raw_pkt is ATM experimental, only for a specific path in PTF + def __init__(self, raw_pkt=None, **fields): + super(Packet, self).__init__() + # Dynamic creation of fields based on fields_desc definition + for field_def in self.fields_desc: + object.__setattr__(self, field_def.name, None) + + if raw_pkt is not None: + warnings.warn( + "raw_pkt is experimental, it is only needed for specific " + "path in PTF, ATM we recommend not to use it" + ) + pkt = self.load_bytes(raw_pkt) + for field_def in self.fields_desc: + setattr(self, field_def.name, pkt.internal_value(field_def.name)) + return + + Base._prepare_kwargs(fields) + for field, value in six.iteritems(fields): + setattr(self, field, value) + + # Perform post-build on fields: + for field_def in self.fields_desc: + field_def.post_build(self) + + def internal_value(self, field_name, default_if_none=False): + # NOTE(sborkows): in order to get raw (internal) value of field, we need + # to surpass __getattribute__ of the Packet class + val = object.__getattribute__(self, field_name) + if val is None and default_if_none: + field_def = self._fields_desc_lookup(field_name) + # noinspection PyProtectedMember + val = ( + field_def._default_value(self) + if callable(field_def._default_value) + else field_def._default_value + ) + return val + + def _cond_field_condition(self, field): + return field.__class__.__name__ != "ConditionalField" or bool( + field.condition(self) + ) + + def _members(self, default_if_none=True, **fields_to_override): + members = {self.name: []} + for field in self.fields_desc: + value = None + if field.name in fields_to_override: + value = fields_to_override[field.name] + elif self._cond_field_condition(field): + value = self.internal_value(field.name, default_if_none=default_if_none) + + if value is None: + continue + + # for IPOptions and TCPOptions + if isinstance(value, list): + value = b"".join(value) + padding = len(value) % 4 + if padding != 0: + value += b"\x00" * (4 - padding) + + size = field.size(value) if callable(field.size) else field.size + if size == 0: + continue + members[self.name].append((field.name, value, size)) + return members + + def _fields_desc_lookup(self, field_name): + return next( + field + for field in object.__getattribute__(self, "fields_desc") + if field.name == str(field_name) + ) + + @property + def hdr_len(self): + return ( + sum( + ( + field.size(getattr(self, field.name)) + if callable(field.size) + else field.size + ) + for field in self.fields_desc + if self._cond_field_condition(field) + ) + // 8 + ) + + def __getattribute__(self, item): + """Besides normal functionality, lookups `field_desc` in order to find a + defined field. If found, returns transformed value (from the internal one). + """ + value = object.__getattribute__(self, str(item)) + if callable(value): + return value + try: + field_def = self._fields_desc_lookup(item) + if value is None: + return field_def.default_value + return field_def.from_internal(value) + except StopIteration: + return value + + def __setattr__(self, key, value): + """Besides normal functionality, lookups `field_desc` in order to find a + defined field. If found, validates new value and if it's ok, transform it to + an internal representation (usually int). + """ + try: + field_def = self._fields_desc_lookup(key) + except StopIteration: + object.__setattr__(self, key, value) + return + + if value is None: + object.__setattr__(self, field_def.name, value) + elif field_def.validate(value): + object.__setattr__(self, field_def.name, field_def.to_internal(value)) + return + else: + raise ValueError( + "Value %s is not valid for field of type %s" + % (repr(value), type(field_def).__name__) + ) + + def __repr__(self): + members = [ + (field.name, getattr(self, field.name)) + for field in self.fields_desc + if self._cond_field_condition(field) + ] + payload = [] + if self._body is not None: + payload.append(repr(self._body)) + return "<{} {} |{}>".format( + self.name, + " ".join(["{}={}".format(x[0], x[1]) for x in members if x[1] is not None]), + " ".join(payload), + ) + + def show(self, indent_lvl=3): + indent = " " * 2 + output_str = "" + inner = self + while inner: + if not isinstance(inner, Base): + output_str += "###[ Raw ]###\n" + output_str += "{}payload= {}\n".format(indent, str(inner)) + break + + try: + members = [ + (field.name, getattr(inner, field.name)) + for field in inner.fields_desc + if inner._cond_field_condition(field) + ] + except AttributeError: + names = next(six.itervalues(inner._all_members())) + members = [(name[0], getattr(inner, name[0])) for name in names] + output_str += "###[ {} ]###\n{}".format( + inner.name, + "".join(["{}{:10s}= {}\n".format(indent, x[0], x[1]) for x in members]), + ) + indent += " " * indent_lvl + inner = inner.body + print(output_str) + + def show2(self, indent_lvl=3): + inner = self + indent = " " * 2 + output_str = "" + while inner: + if not isinstance(inner, Base): + output_str += "###[ Raw ]###\n" + output_str += "{}payload= {}\n".format(indent, str(inner)) + break + + members = list(inner.members.values())[0] + output_str += "###[ {} ]###\n".format(inner.name) + for name in members: + try: + field = inner._fields_desc_lookup(name[0]) + field_value = getattr(inner, field.name) + if field_value is None and callable(field._default_value): + field_value = field._default_value(inner) + output_str += "{}{:10s}= {}\n".format( + indent, field.name, field_value + ) + except AttributeError: + output_str += "{}{:10s}= {}\n".format(indent, name[0], name[1]) + indent += " " * indent_lvl + inner = inner.body + print(output_str) diff --git a/src/bf_pktpy/library/specs/pretty.py b/src/bf_pktpy/library/specs/pretty.py new file mode 100644 index 0000000..2543bbb --- /dev/null +++ b/src/bf_pktpy/library/specs/pretty.py @@ -0,0 +1,308 @@ +#!/usr/bin/env python + + +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" Pretty module to transform code output """ +import json +import re +import six +import types + + +# ============================================================================= +def todict(data): + """convert object structure to dictionary""" + if isinstance(data, dict): + new_dict = {} + for key, value in data.items(): + new_dict[key] = todict(value) + return new_dict + if isinstance(data, tuple): + new_dict = {} + if len(data) == 2: + if isinstance( + data[0], (six.string_types, six.binary_type, six.integer_types) + ): + if ( + isinstance( + data[1], (six.string_types, six.binary_type, six.integer_types) + ) + or isinstance(data[1], (list, tuple)) + and not data[1] + ): + new_dict = {data[0]: data[1]} + elif isinstance(data[1], (list, tuple)) and data[1]: + if isinstance( + data[1][0], + (six.string_types, six.binary_type, six.integer_types), + ): + new_dict = {data[0]: data[1]} + elif isinstance(data[1][0], (tuple)): + new_dict = {data[0]: todict(data[1])} + if new_dict: + return new_dict + for each in data: + new_dict.update(todict(each)) + return new_dict + if isinstance(data, list): + new_list = [] + for each in data: + new_list.append(todict(each)) + return new_list + return data + + +def _print(ftype, indent=0, key="", data="", is_dash=False, constant=False): + """Standard out""" + + def protect(data, strip=False): + """Protect key from misread""" + if not strip: + data = list(data) + data[1:1] = "__" + return "".join(data) + return re.sub("__", "", data) + + spaces = " " * indent + if ftype == "yaml": + spaces = spaces + "- " if is_dash else spaces + if constant: + print(spaces + str(key)) + else: + line = spaces + str(key) + ": " + str(data) + if isinstance(data, str) and data.isdigit(): + line = line.ljust(40) + "0x" + str(hex(int(data)))[2:].upper() + print(line) + if ftype == "json": + for ikey in sorted(re.findall(r"\"(\S+)\":", data), key=len, reverse=True): + pkey = protect(ikey) + data = re.sub('"' + ikey + '"', '"' + pkey + '"', data) + for ikey in sorted(re.findall(r":\s*\"(\S+)\"", data), key=len, reverse=True): + if not re.findall(r"[*\/_]", ikey): + data = re.sub('"' + ikey + '"', '"' + ikey + '"', data) + data = protect(data, strip=True) + print(data) + + +def pretty(obj, style="yaml"): + """Pretty print obj structure""" + + def maxlength(obj): + """Find max key length""" + size = 0 + for each in obj: + if isinstance(each, tuple) and size < len(each[0]): + size = len(each[0]) + return size + + def printline(indent, key, value, is_dash=False): + if isinstance(value, (six.string_types, six.binary_type, six.integer_types)): + _print("yaml", indent, key, str(value), is_dash) + else: + _print("yaml", indent, key, is_dash=is_dash) + align(value, indent + 4) + + def align(obj, indent=0): + """Recursively align and print""" + if isinstance(obj, dict): + if len(obj) == 1: + key, value = next(iter(obj.items())) + printline(indent, key, value, False) + else: + for key, value in obj.items(): + align({key: value}, indent) + if isinstance(obj, list): + key_length = maxlength(obj) + in_loop = False + for each in obj: + if not in_loop: + is_first = True + if indent == 0 and isinstance(each, tuple): + key, value = each[0].ljust(key_length), each[1] + _print("yaml", 0, key, value) + elif isinstance(each, tuple): + if len(each) == 2: + if isinstance( + each[0], (six.binary_type, six.string_types) + ) and isinstance(each[1], (six.binary_type, six.string_types)): + if is_first: + printline(indent - 2, each[0], each[1], True) + is_first, in_loop = False, True + else: + printline(indent, each[0], each[1]) + elif len(each) > 1: + for item in each: + key, value = item[0].ljust(key_length), item[1] + if is_first: + printline(indent - 2, key, value, True) + is_first = False + else: + printline(indent, key, value) + else: + key, value = each[0].ljust(key_length), each[1] + if is_first: + printline(indent - 2, key, value, True) + is_first = False + else: + printline(indent, key, value) + elif isinstance(each, dict): + for key, value in each.items(): + if is_first: + printline(indent - 2, key, value, True) + is_first = False + else: + align({key: value}, indent) + elif isinstance( + each, (six.binary_type, six.string_types, six.integer_types) + ): + _print("yaml", indent - 2, each, is_dash=True, constant=True) + else: + align(each, indent) + if isinstance(obj, tuple): + if len(obj) == 2 and isinstance( + obj[0], (six.binary_type, six.string_types) + ): + key, value = obj + printline(indent, key, value, False) + else: + key_length = maxlength(obj) + for each in obj: + if isinstance(each, tuple): + key, value = each[0].ljust(key_length), each[1] + printline(indent, key, value, False) + else: + align(each, indent) + + def transform(data, style="yaml"): + """Prettify object""" + if style == "yaml": + align(data) + else: + _print("json", data=json.dumps(todict(data), indent=4)) + + def callf(*args, **kwargs): + """Transform obj into yaml or json output""" + style = "yaml" + if len(args) >= 2: + if args[1] in "yaml json".split(): + style = args[1] + data = obj(*args, **kwargs) + transform(data, style, **kwargs) + + # Use as decorator + if isinstance(obj, types.FunctionType): + return callf + + # Use as function + if style in "yaml json".split(): + return transform(obj, style) + raise ValueError("Only supports json and yaml output format") + + +def _print_line(lline, wcount): + for each in lline: + print(each, "") + print(" " * (81 - wcount)) + + +def docnote(doc): + """Pretty print class __doc__""" + is_header = True + + print() + for line in doc.split("\n"): + line = line.rstrip() + newline = [] + # if not len(line.split()): + # is_header = False + if is_header: + _print_line([line], len(line)) + is_header = False + continue + if len(line.split()) == 1: + word = line.split()[0] + if word.lower() in "usage: or definition: example: note:": + _print_line([line], len(line)) + continue + if "(optional)" in line: + temp = re.sub("optional", "|", line) + left, right = temp.split("|", 1) + newline.extend([left, "optional", right]) + if "(required)" in line: + temp = re.sub("required", "|", line) + left, right = temp.split("|", 1) + newline.extend([left, "required", right]) + + if re.findall(r"=\s+[A-Za-z]", line) or re.findall(r"=\s+\W+[A-Za-z]", line): + left, right = line.split("=", 1) + newline.extend([left, "="]) + if "(" in right: + cls, rest = right.split("(", 1) + newline.extend([cls, "(" + rest]) + elif "{" in right: + cls, rest = right.split("{", 1) + newline.extend([cls, "{" + rest]) + else: + newline.append(right) + + if not newline: + newline.append(line) + + _print_line(newline, len(line)) + + +def footnote(instances): + """Pretty print additional help""" + if instances: + _print_line(["-" * 87], 81) + line = (" This class contains one or more objects. To learn" " about:").ljust( + 87 + ) + _print_line([line], len(line)) + new_dict = {} + # reverse key-value for sorting + for name, parent in instances.items(): + if not parent: + parent = name.title() + new_dict[parent] = name + # print helper + for parent in sorted(new_dict): + helper = parent + ".help()" + newline = [] + newline.append(" _ ") + newline.append(new_dict[parent]) + newline.append(", type ") + newline.append(helper) + _print_line(newline, len(new_dict[parent]) + len(helper) + 9) + + extra = [] + extra.append(["-" * 87]) + extra.append([" For quick helps, use".ljust(87)]) + extra.append([" _ .help()".ljust(87)]) + extra.append( + [ + ( + " _ .info(), info() takes optional parameter " + '"json" or "yaml"' + ).ljust(87) + ] + ) + extra.append(["-" * 87]) + for each in extra: + _print_line(each, 81) + print() + + +# ============================================================================= diff --git a/src/bf_pktpy/library/specs/tcp.py b/src/bf_pktpy/library/specs/tcp.py new file mode 100644 index 0000000..5fd34af --- /dev/null +++ b/src/bf_pktpy/library/specs/tcp.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python + + +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" TCP class """ +from bf_pktpy.library.specs.container import Container +from bf_pktpy.library.specs.templates.tcp import TCP as TCPTemplate + + +# ============================================================================= +class TCP(Container): + """TCP class""" + + fields = ( + "sport dport seq ack dataofs reserved flags window chksum " "urgptr options" + ).split() + + def __init__(self, **kwargs): + super(TCP, self).__init__(TCPTemplate, **kwargs) + + def __truediv__(self, payload): + self.clear() # not done + return self + + +# ============================================================================= diff --git a/src/bf_pktpy/library/specs/templates/__init__.py b/src/bf_pktpy/library/specs/templates/__init__.py new file mode 100644 index 0000000..20cb2e7 --- /dev/null +++ b/src/bf_pktpy/library/specs/templates/__init__.py @@ -0,0 +1,81 @@ +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from bf_pktpy.library.specs.templates.ethernet import Ether as EtherTemplate +from bf_pktpy.library.specs.templates.ipv4 import IP as IPv4Template +from bf_pktpy.library.specs.templates.ipv6 import IPv6 as IPv6Template +from bf_pktpy.library.specs.templates.tcp import TCP as TCPTemplate +from bf_pktpy.library.specs.templates.udp import UDP as UDPTemplate +from bf_pktpy.library.specs.templates.icmp import ICMP as ICMPTemplate +from bf_pktpy.library.specs.templates.igmp import IGMP as IGMPTemplate +from bf_pktpy.library.specs.templates.icmpv6_unknown import ( + ICMPv6Unknown as ICMPv6Template, +) +from bf_pktpy.library.specs.templates.mpls import MPLS as MPLSTemplate +from bf_pktpy.library.specs.templates.gre import GRE as GRETemplate +from bf_pktpy.library.specs.templates.dot1q import Dot1Q as Dot1QTemplate +from bf_pktpy.library.specs.templates.arp import ARP as ARPTemplate +from bf_pktpy.library.specs.templates.bfd import BFD as BFDTemplate +from bf_pktpy.library.specs.templates.bootp import BOOTP as BOOTPTemplate +from bf_pktpy.library.specs.templates.dhcp import DHCP as DHCPTemplate +from bf_pktpy.library.specs.templates.erspan import ERSPAN as ERSPAN_Template +from bf_pktpy.library.specs.templates.erspan import ERSPAN_II as ERSPAN_IITemplate +from bf_pktpy.library.specs.templates.erspan import ERSPAN_III as ERSPAN_IIITemplate +from bf_pktpy.library.specs.templates.erspan import ( + ERSPAN_PlatformSpecific as ERSPAN_PlatformSpecificTemplate, +) +from bf_pktpy.library.specs.templates.vxlan import VXLAN as VXLANTemplate + + +def clone(protocol): + """Clone to a new object""" + class_name = protocol.__class__.__name__ + if class_name == "Ether": + return EtherTemplate(**protocol.parameters) + if class_name == "IP": + return IPv4Template(**protocol.parameters) + if class_name == "IPv6": + return IPv6Template(**protocol.parameters) + if class_name == "TCP": + return TCPTemplate(**protocol.parameters) + if class_name == "UDP": + return UDPTemplate(**protocol.parameters) + if class_name == "ICMP": + return ICMPTemplate(**protocol.parameters) + if class_name == "ICMPv6Unknown": + return ICMPv6Template(**protocol.parameters) + if class_name == "MPLS": + return MPLSTemplate(**protocol.parameters) + if class_name == "GRE": + return GRETemplate(**protocol.parameters) + if class_name == "Dot1Q": + return Dot1QTemplate(**protocol.parameters) + if class_name == "ARP": + return ARPTemplate(**protocol.parameters) + if class_name == "BFD": + return BFDTemplate(**protocol.parameters) + if class_name == "BOOTP": + return BOOTPTemplate(**protocol.parameters) + if class_name == "DHCP": + return DHCPTemplate(**protocol.parameters) + if class_name == "VXLAN": + return VXLANTemplate(**protocol.parameters) + if class_name == "ERSPAN": + return ERSPAN_Template(**protocol.parameters) + if class_name == "ERSPAN_II": + return ERSPAN_IITemplate(**protocol.parameters) + if class_name == "ERSPAN_III": + return ERSPAN_IIITemplate(**protocol.parameters) + if class_name == "PlatformSpecific": + return ERSPAN_PlatformSpecificTemplate(**protocol.parameters) + if class_name == "IGMP": + return IGMPTemplate(**protocol.parameters) diff --git a/src/bf_pktpy/library/specs/templates/arp.py b/src/bf_pktpy/library/specs/templates/arp.py new file mode 100644 index 0000000..785c210 --- /dev/null +++ b/src/bf_pktpy/library/specs/templates/arp.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python + + +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" ARP template """ +import ipaddress +import six + +from bf_pktpy.library.helpers.mac import correct_mac +from bf_pktpy.library.specs.base import Base +from bf_pktpy.library.specs.validate import remove_unicode + +# ============================================================================= + + +class ARP(Base): + """ARP class + + ARP + hwtype (int) + ptype (int) + hwlen (int) + plen (int) + op (int) + hwsrc (str) + psrc (str) + hwdst (str) + pdst (str) + + Examples: + | + create + | arp = ARP(hwtype=.., ptype=.., ..) + | + make change + | arp.hwtype = + | arp.ptype = + | arp.hwlen = + | arp.plen = + | ... + """ + + name = "ARP" + + _hwsrc = "00:00:00:00:00:00" + _hwdst = "00:00:00:00:00:00" + + def __init__(self, **kwargs): + Base._prepare_kwargs(kwargs) + + super(ARP, self).__init__() + self.hwtype = kwargs.pop("hwtype", 1) + self.ptype = kwargs.pop("ptype", 2048) + self.hwlen = kwargs.pop("hwlen", 6) + self.plen = kwargs.pop("plen", 4) + self.op = kwargs.pop("op", 1) + self.hwsrc = kwargs.pop("hwsrc", "00:00:00:00:00:00") + self.psrc = kwargs.pop("psrc", "0.0.0.0") + self.hwdst = kwargs.pop("hwdst", "00:00:00:00:00:00") + self.pdst = kwargs.pop("pdst", "0.0.0.0") + if kwargs: + raise ValueError("Unsupported key(s) %s" % list(kwargs.keys())) + + @property + def hwsrc(self): + return self._hwsrc + + @hwsrc.setter + def hwsrc(self, value): + value = remove_unicode(value) + self._hwsrc = correct_mac(value) + + @property + def hwdst(self): + return self._hwdst + + @hwdst.setter + def hwdst(self, value): + value = remove_unicode(value) + self._hwdst = correct_mac(value) + + @property + def hdr_len(self): + """Get header size""" + return 28 + + def _members(self): + """Member information""" + hwdst = self.hwdst + hwsrc = self.hwsrc + + if isinstance(hwdst, str): + hwdst = int(self.hwdst.replace(":", "").replace(".", ""), 16) + if isinstance(hwsrc, str): + hwsrc = int(self.hwsrc.replace(":", "").replace(".", ""), 16) + + psrc = self.psrc + pdst = self.pdst + + if isinstance(psrc, (six.string_types, six.binary_type)): + psrc = int(ipaddress.IPv4Address(six.ensure_text(self.psrc))) + + if isinstance(pdst, (six.string_types, six.binary_type)): + pdst = int(ipaddress.IPv4Address(six.ensure_text(self.pdst))) + + members = ( + ("hwtype", self.hwtype, 16), + ("ptype", self.ptype, 16), + ("hwlen", self.hwlen, 8), + ("plen", self.plen, 8), + ("op", self.op, 16), + ("hwsrc", hwsrc, 48), + ("psrc", psrc, 32), + ("hwdst", hwdst, 48), + ("pdst", pdst, 32), + ) + return {"arp": members} + + +# ============================================================================= diff --git a/src/bf_pktpy/library/specs/templates/bfd.py b/src/bf_pktpy/library/specs/templates/bfd.py new file mode 100644 index 0000000..5a63909 --- /dev/null +++ b/src/bf_pktpy/library/specs/templates/bfd.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python + + +# Copyright (c) 2022 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" BFD template """ +from bf_pktpy.library.specs.packet import Packet +from bf_pktpy.library.fields import ( + BitField, + BitEnumField, + FlagsField, + ByteField, + IntField, +) + + +# ============================================================================= + + +diagnostic_types = { + 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", +} + +status_names = { + 0: "AdminDown", + 1: "Down", + 2: "Init", + 3: "Up", +} + + +class BFD(Packet): + """ + BFD class + Bidirectional Forwarding Detection + + BFD + version (int) + diag (int) + sta (int) + flags (int) + detect_mult (int) + len (int) + my_discriminator (int) + your_discriminator (int) + min_tx_interval (int) + min_rx_interval (int) + echo_rx_interval (int) + + Examples: + | + create + | bfd = BFD(version=..., diag=..., ..) + | + make change + | bfd.version = + | bfd.diag = + """ + + name = "BFD" + fields_desc = [ + BitField("version", 1, 3), + BitEnumField("diag", 0, 5, diagnostic_types), + BitEnumField("sta", 3, 2, status_names), + FlagsField("flags", 0x00, 6, "MDACFP"), + ByteField("detect_mult", 3), + ByteField("len", 24), + IntField("my_discriminator", 286331153), + IntField("your_discriminator", 572662306), + IntField("min_tx_interval", 1000000000), + IntField("min_rx_interval", 1000000000), + IntField("echo_rx_interval", 1000000000), + ] + + @property + def hdr_len(self): + return 24 + + @property + def diagnostic_code(self): + return diagnostic_types.get(self.diag) + + @property + def session_state(self): + return status_names.get(self.sta) + + +# ============================================================================= diff --git a/src/bf_pktpy/library/specs/templates/bootp.py b/src/bf_pktpy/library/specs/templates/bootp.py new file mode 100644 index 0000000..623b4d9 --- /dev/null +++ b/src/bf_pktpy/library/specs/templates/bootp.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python + + +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" BOOTP template """ +from bf_pktpy.library.specs.base import Base + + +# ============================================================================= +_magic = 0x63825363 + + +class BOOTP(Base): + """ + BOOTP class + Bootstrap Protocol + + Definition: + BOOTP + op (int) + htype (int) + hlen (int) + hops (int) + xid (int) + secs (int) + flags (int) + ciaddr (str) + yiaddr (str) + siaddr (str) + giaddr (str) + chaddr (str) + sname (str) + file (str) + options (str) + + Examples: + | + create + | bootp = BOOTP(op=.., htype=.., ) + | + make change + | bootp.op = + | bootp.htype = + | ... + """ + + name = "BOOTP" + + def __init__(self, **kwargs): + Base._prepare_kwargs(kwargs) + + super(BOOTP, self).__init__() + self.op = kwargs.pop("op", 1) + self.htype = kwargs.pop("htype", 1) + self.hlen = kwargs.pop("hlen", 6) + self.hops = kwargs.pop("hops", 0) + self.xid = kwargs.pop("xid", 0) + self.secs = kwargs.pop("secs", 0) + self.flags = kwargs.pop("flags", 0) + self.ciaddr = kwargs.pop("ciaddr", "") + self.yiaddr = kwargs.pop("yiaddr", "") + self.siaddr = kwargs.pop("siaddr", "0.0.0.0") + self.giaddr = kwargs.pop("giaddr", "0.0.0.0") + self.chaddr = kwargs.pop("chaddr", "") + self.sname = kwargs.pop("sname", "") + self.file = kwargs.pop("file", "") + self.options = kwargs.pop("options", 0) + + if kwargs: + raise ValueError("Unsupported key(s) %s" % list(kwargs.keys())) + + def _combine(self, body_copy): + if body_copy.name == "DHCP": + self._body = body_copy + self.options = _magic + return self + + raise ValueError("Unsupported binding") + + @property + def total_len(self): + """Get full length""" + if self._body: + return self.hdr_len + len(self._body) + return self.hdr_len + + def _members(self): + members = ( + ("op", self.op, 8), + ("htype", self.htype, 8), + ("hlen", self.hlen, 8), + ("hops", self.hops, 8), + ("xid", self.xid, 32), + ("secs", self.secs, 16), + ("flags", self.flags, 16), + ("ciaddr", self.ciaddr, 32), + ("yiaddr", self.yiaddr, 32), + ("siaddr", self.siaddr, 32), + ("giaddr", self.giaddr, 32), + ("chaddr", self.chaddr, 128), + ("sname", self.sname, 512), + ("file", self.file, 1024), + ("options", self.options, 0), + ) + return {"bootp": members} + + +# ============================================================================= diff --git a/src/bf_pktpy/library/specs/templates/control.py b/src/bf_pktpy/library/specs/templates/control.py new file mode 100644 index 0000000..0459ce3 --- /dev/null +++ b/src/bf_pktpy/library/specs/templates/control.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python + + +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" Stream control template """ +from bf_pktpy.library.specs.base import Base + + +# ============================================================================= +class StreamControl(Base): + """Stream Control class + + StreamControl + rate (int): rate to send traffic + unit (str): send unit + mode (str):send mode + + Examples: + | stream_control = StreamControl(rate=1000000) + | stream_control.rate = 25000000000 + """ + + def __init__(self, rate=1000000, unit="kbps", mode="continuous"): + self.rate = rate + self.unit = unit + self.mode = mode + + def _members(self): + """Member information""" + members = (("rate", self.rate), ("unit", self.unit), ("mode", self.mode)) + return {"stream_control": members} + + +# ============================================================================= diff --git a/src/bf_pktpy/library/specs/templates/cpu/__init__.py b/src/bf_pktpy/library/specs/templates/cpu/__init__.py new file mode 100644 index 0000000..f68df39 --- /dev/null +++ b/src/bf_pktpy/library/specs/templates/cpu/__init__.py @@ -0,0 +1,41 @@ +# Copyright (c) 2022 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from bf_pktpy.library.specs.templates.cpu.dtel_report_hdr import DtelReportHdr +from bf_pktpy.library.specs.templates.cpu.dtel_report_v2_hdr import DtelReportV2Hdr +from bf_pktpy.library.specs.templates.cpu.fabric_cpu_bfd_event_header import ( + FabricCpuBfdEventHeader, +) +from bf_pktpy.library.specs.templates.cpu.fabric_cpu_header import FabricCpuHeader +from bf_pktpy.library.specs.templates.cpu.fabric_cpu_sflow_header import ( + FabricCpuSflowHeader, +) +from bf_pktpy.library.specs.templates.cpu.fabric_cpu_timestamp_header import ( + FabricCpuTimestampHeader, +) +from bf_pktpy.library.specs.templates.cpu.fabric_header import FabricHeader +from bf_pktpy.library.specs.templates.cpu.fabric_multicast_header import ( + FabricMulticastHeader, +) +from bf_pktpy.library.specs.templates.cpu.fabric_payload_header import ( + FabricPayloadHeader, +) +from bf_pktpy.library.specs.templates.cpu.fabric_unicast_header import ( + FabricUnicastHeader, +) +from bf_pktpy.library.specs.templates.cpu.mod_header import ModHeader +from bf_pktpy.library.specs.templates.cpu.postcard_header import PostcardHeader +from bf_pktpy.library.specs.templates.cpu.simple_l3_mirror_cpu_header import ( + SimpleL3SwitchCpuHeader, +) + +from bf_pktpy.library.specs.templates.cpu.mirror_pre_deparser import MirrorPreDeparser diff --git a/src/bf_pktpy/library/specs/templates/cpu/dtel_report_hdr.py b/src/bf_pktpy/library/specs/templates/cpu/dtel_report_hdr.py new file mode 100644 index 0000000..5318c35 --- /dev/null +++ b/src/bf_pktpy/library/specs/templates/cpu/dtel_report_hdr.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python + + +# Copyright (c) 2022 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" DtelReportHdr template """ +from bf_pktpy.library.specs.packet import Packet +from bf_pktpy.library.fields import BitField, IntField + + +class DtelReportHdr(Packet): + name = "DtelReportHdr" + fields_desc = [ + BitField("ver", 0, 4), + BitField("next_proto", 0, 4), + BitField("dropped", 0, 1), + BitField("congested_queue", 0, 1), + BitField("path_tracking_flow", 0, 1), + BitField("reserved", 0, 15), + BitField("hw_id", 0, 6), + IntField("sequence_number", 0), + IntField("timestamp", 0), + ] + + def _combine(self, body_copy): + if body_copy.name == "Ether": + self._body = body_copy + self.next_proto = 0 + return self + if body_copy.name == "ModHeader": + self._body = body_copy + self.next_proto = 1 + return self + if body_copy.name == "PostcardHeader": + self._body = body_copy + self.next_proto = 2 + return self + + raise ValueError("Unsupported binding") diff --git a/src/bf_pktpy/library/specs/templates/cpu/dtel_report_v2_hdr.py b/src/bf_pktpy/library/specs/templates/cpu/dtel_report_v2_hdr.py new file mode 100644 index 0000000..a5aeecd --- /dev/null +++ b/src/bf_pktpy/library/specs/templates/cpu/dtel_report_v2_hdr.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python + + +# Copyright (c) 2022 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" DtelReportV2Hdr template """ +from bf_pktpy.library.specs.packet import Packet +from bf_pktpy.library.fields import ( + BitField, + ByteField, + ShortField, + ThreeBytesField, + IntField, + XLongField, + ConditionalField, +) + + +def calculate_md_length(pkt): + return bin(pkt.rep_md_bits).count("1") + bin(pkt.rep_md_bits & 0x0E00).count("1") + + +class DtelReportV2Hdr(Packet): + name = "DtelReportV2Hdr" + fields_desc = [ + BitField("ver", 0, 4), + BitField("hw_id", 0, 6), + BitField("sequence_number", 0, 22), + IntField("switch_id", 0), + BitField("rep_type", 0, 4), + BitField("in_type", 0, 4), + ByteField("report_length", 0), + ByteField("md_length", calculate_md_length), + BitField("dropped", 0, 1), + BitField("congested_queue", 0, 1), + BitField("path_tracking_flow", 0, 1), + BitField("reserved", 0, 5), + ShortField("rep_md_bits", 0), + ShortField("domain_specific_id", 0), + ShortField("ds_md_bits", 0), + ShortField("ds_md_status", 0), + # level_1_if_ids + ConditionalField( + ShortField("ingress_port", 0), lambda pkt: pkt.rep_md_bits & 0x4000 + ), + ConditionalField( + ShortField("egress_port", 0), lambda pkt: pkt.rep_md_bits & 0x4000 + ), + # hop_latency + ConditionalField( + IntField("hop_latency", 0), lambda pkt: pkt.rep_md_bits & 0x2000 + ), + # queue_occupancy + ConditionalField( + ByteField("queue_id", 0), lambda pkt: pkt.rep_md_bits & 0x1000 + ), + ConditionalField( + ThreeBytesField("queue_depth", 0), lambda pkt: pkt.rep_md_bits & 0x1000 + ), + # ingress_tstamp + ConditionalField( + XLongField("timestamp", 0), lambda pkt: pkt.rep_md_bits & 0x0800 + ), + # egress_tstamp + ConditionalField( + XLongField("egress_tstamp", 0), lambda pkt: pkt.rep_md_bits & 0x0400 + ), + # level_2_if_ids + ConditionalField( + IntField("ingress_if_id", 0), lambda pkt: pkt.rep_md_bits & 0x0200 + ), + ConditionalField( + IntField("egress_if_id", 0), lambda pkt: pkt.rep_md_bits & 0x0200 + ), + # eg_port_tx_util + ConditionalField( + IntField("eg_port_tx_util", 0), lambda pkt: pkt.rep_md_bits & 0x0100 + ), + # buffer_occupancy + ConditionalField( + ByteField("buffer_id", 0), lambda pkt: pkt.rep_md_bits & 0x0080 + ), + ConditionalField( + ThreeBytesField("buffer_occupancy", 0), lambda pkt: pkt.rep_md_bits & 0x0080 + ), + # drop_reason + ConditionalField( + ByteField("drop_queue_id", 0), lambda pkt: pkt.rep_md_bits & 0x0001 + ), + ConditionalField( + ByteField("drop_reason", 0), lambda pkt: pkt.rep_md_bits & 0x0001 + ), + ConditionalField( + ShortField("drop_reserved", 0), lambda pkt: pkt.rep_md_bits & 0x0001 + ), + ] + + def _combine(self, body_copy): + if body_copy.name == "Ether": + self._body = body_copy + self.in_type = 3 + return self + + raise ValueError("Unsupported binding") diff --git a/src/bf_pktpy/library/specs/templates/cpu/fabric_cpu_bfd_event_header.py b/src/bf_pktpy/library/specs/templates/cpu/fabric_cpu_bfd_event_header.py new file mode 100644 index 0000000..b485885 --- /dev/null +++ b/src/bf_pktpy/library/specs/templates/cpu/fabric_cpu_bfd_event_header.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python + + +# Copyright (c) 2022 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" FabricCpuBfdEventHeader template """ +from bf_pktpy.library.specs.packet import Packet +from bf_pktpy.library.fields import ShortField + + +class FabricCpuBfdEventHeader(Packet): + name = "FabricCpuBfdEventHeader" + fields_desc = [ShortField("bfd_sid", 0), ShortField("bfd_event", 0)] + + def _combine(self, body_copy): + if body_copy.name in ("FabricCpuTimestampHeader", "FabricPayloadHeader"): + self._body = body_copy + return self + + raise ValueError("Unsupported binding") diff --git a/src/bf_pktpy/library/specs/templates/cpu/fabric_cpu_header.py b/src/bf_pktpy/library/specs/templates/cpu/fabric_cpu_header.py new file mode 100644 index 0000000..37f1127 --- /dev/null +++ b/src/bf_pktpy/library/specs/templates/cpu/fabric_cpu_header.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python + + +# Copyright (c) 2022 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" FabricCpuHeader template """ +from bf_pktpy.library.specs.packet import Packet +from bf_pktpy.library.fields import BitField, ShortField + + +class FabricCpuHeader(Packet): + name = "FabricCpuHeader" + fields_desc = [ + BitField("tx_bypass", 0, 1), + BitField("reserved1", 0, 2), + BitField("egress_queue", 0, 5), + ShortField("ingress_port", 0), + ShortField("port_lag_index", 0), + ShortField("ingress_bd", 0), + ShortField("reason_code", 0), + ] + + def _combine(self, body_copy): + if body_copy.name in ( + "FabricCpuSflowHeader", + "FabricCpuBfdEventHeader", + "FabricCpuTimestampHeader", + "FabricPayloadHeader", + ): + self._body = body_copy + return self + + raise ValueError("Unsupported binding") diff --git a/src/bf_pktpy/library/specs/templates/cpu/fabric_cpu_sflow_header.py b/src/bf_pktpy/library/specs/templates/cpu/fabric_cpu_sflow_header.py new file mode 100644 index 0000000..c87bd8f --- /dev/null +++ b/src/bf_pktpy/library/specs/templates/cpu/fabric_cpu_sflow_header.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python + + +# Copyright (c) 2022 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" FabricCpuSflowHeader template """ +from bf_pktpy.library.specs.packet import Packet +from bf_pktpy.library.fields import ShortField + + +class FabricCpuSflowHeader(Packet): + name = "FabricCpuSflowHeader" + fields_desc = [ShortField("sflow_sid", 0)] + + def _combine(self, body_copy): + if body_copy.name in ( + "FabricCpuBfdEventHeader", + "FabricCpuTimestampHeader", + "FabricPayloadHeader", + ): + self._body = body_copy + return self + + raise ValueError("Unsupported binding") diff --git a/src/bf_pktpy/library/specs/templates/cpu/fabric_cpu_timestamp_header.py b/src/bf_pktpy/library/specs/templates/cpu/fabric_cpu_timestamp_header.py new file mode 100644 index 0000000..7d72dbe --- /dev/null +++ b/src/bf_pktpy/library/specs/templates/cpu/fabric_cpu_timestamp_header.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python + + +# Copyright (c) 2022 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" FabricCpuTimestampHeader template """ +from bf_pktpy.library.specs.packet import Packet +from bf_pktpy.library.fields import ThreeBytesField + + +class FabricCpuTimestampHeader(Packet): + name = "FabricCpuTimestampHeader" + fields_desc = [ + ThreeBytesField("arrival_time_0", 0), + ThreeBytesField("arrival_time_1", 0), + ] + + def _combine(self, body_copy): + if body_copy.name == "FabricPayloadHeader": + self._body = body_copy + return self + + raise ValueError("Unsupported binding") diff --git a/src/bf_pktpy/library/specs/templates/cpu/fabric_header.py b/src/bf_pktpy/library/specs/templates/cpu/fabric_header.py new file mode 100644 index 0000000..95af7c6 --- /dev/null +++ b/src/bf_pktpy/library/specs/templates/cpu/fabric_header.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python + + +# Copyright (c) 2022 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" FabricHeader template """ +from bf_pktpy.library.specs.packet import Packet +from bf_pktpy.library.fields import BitField, ByteField + + +class FabricHeader(Packet): + name = "FabricHeader" + fields_desc = [ + BitField("packet_type", 0, 3), + BitField("header_version", 0, 2), + BitField("packet_version", 0, 2), + BitField("pad1", 0, 1), + BitField("fabric_color", 0, 3), + BitField("fabric_qos", 0, 5), + ByteField("dst_device", 0), + ] + + def _combine(self, body_copy): + if body_copy.name in ( + "FabricCpuHeader", + "FabricUnicastHeader", + "FabricMulticastHeader", + ): + self._body = body_copy + return self + + raise ValueError("Unsupported binding") diff --git a/src/bf_pktpy/library/specs/templates/cpu/fabric_multicast_header.py b/src/bf_pktpy/library/specs/templates/cpu/fabric_multicast_header.py new file mode 100644 index 0000000..9951f3a --- /dev/null +++ b/src/bf_pktpy/library/specs/templates/cpu/fabric_multicast_header.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python + + +# Copyright (c) 2022 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" FabricMulticastHeader template """ +from bf_pktpy.library.specs.packet import Packet +from bf_pktpy.library.fields import BitField, ShortField + + +class FabricMulticastHeader(Packet): + name = "FabricMulticastHeader" + fields_desc = [ + BitField("routed", 0, 1), + BitField("outerRouted", 0, 1), + BitField("tunnelTerminate", 0, 1), + BitField("ingressTunnelType", 0, 5), + ShortField("ingressIfindex", 0), + ShortField("ingressBd", 0), + ShortField("mcastGrpA", 0), + ShortField("mcastGrpB", 0), + ShortField("ingressRid", 0), + ShortField("l1ExclusionId", 0), + ] + + def _combine(self, body_copy): + if body_copy.name == "FabricPayloadHeader": + self._body = body_copy + return self + + raise ValueError("Unsupported binding") diff --git a/src/bf_pktpy/library/specs/templates/cpu/fabric_payload_header.py b/src/bf_pktpy/library/specs/templates/cpu/fabric_payload_header.py new file mode 100644 index 0000000..2cedbce --- /dev/null +++ b/src/bf_pktpy/library/specs/templates/cpu/fabric_payload_header.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python + + +# Copyright (c) 2022 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" FabricPayloadHeader template """ +from bf_pktpy.library.specs.packet import Packet +from bf_pktpy.library.fields import ShortField + + +class FabricPayloadHeader(Packet): + name = "FabricPayloadHeader" + fields_desc = [ShortField("ether_type", 0)] + + def _combine(self, body_copy): + if body_copy.name == "DotAD": + self._body = body_copy + self.ether_type = 0x88A8 + return self + if body_copy.name == "Dot1Q": + self._body = body_copy + self.ether_type = 0x8100 + return self + if body_copy.name == "ARP": + self._body = body_copy + self.ether_type = 0x0806 + return self + if body_copy.name == "IP": + self._body = body_copy + self.ether_type = 0x0800 + return self + if body_copy.name == "IPv6": + self._body = body_copy + self.ether_type = 0x86DD + return self + if body_copy.name == "MPLS": + self._body = body_copy + self.ether_type = 0x8847 + return self + if body_copy.name == "ERSPAN_III": + self._body = body_copy + self._body.o = 0 + return self + + raise ValueError("Unsupported binding") diff --git a/src/bf_pktpy/library/specs/templates/cpu/fabric_unicast_header.py b/src/bf_pktpy/library/specs/templates/cpu/fabric_unicast_header.py new file mode 100644 index 0000000..f909306 --- /dev/null +++ b/src/bf_pktpy/library/specs/templates/cpu/fabric_unicast_header.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python + + +# Copyright (c) 2022 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" FabricUnicastHeader template """ +from bf_pktpy.library.specs.packet import Packet +from bf_pktpy.library.fields import BitField, ShortField + + +class FabricUnicastHeader(Packet): + name = "FabricUnicastHeader" + fields_desc = [ + BitField("routed", 0, 1), + BitField("outerRouted", 0, 1), + BitField("tunnelTerminate", 0, 1), + BitField("ingressTunnelType", 0, 5), + ShortField("nexthopIndex", 0), + ] + + def _combine(self, body_copy): + if body_copy.name == "FabricPayloadHeader": + self._body = body_copy + return self + + raise ValueError("Unsupported binding") diff --git a/src/bf_pktpy/library/specs/templates/cpu/mirror_pre_deparser.py b/src/bf_pktpy/library/specs/templates/cpu/mirror_pre_deparser.py new file mode 100644 index 0000000..ac3e8ae --- /dev/null +++ b/src/bf_pktpy/library/specs/templates/cpu/mirror_pre_deparser.py @@ -0,0 +1,20 @@ +# Copyright (c) 2022 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +from bf_pktpy.library.specs.packet import Packet +from bf_pktpy.library.fields import ByteField, ShortField + + +class MirrorPreDeparser(Packet): + name = "MirrorIntMdHeader" + fields_desc = [ + ByteField("pkt_type", 0x01), + ByteField("do_egr_mirroring", 0x01), + ShortField("sid", 0x00), + ] + + def _combine(self, body_copy): + if body_copy.name == "Ether": + self._body = body_copy + return self + + raise ValueError("Unsupported binding") diff --git a/src/bf_pktpy/library/specs/templates/cpu/mod_header.py b/src/bf_pktpy/library/specs/templates/cpu/mod_header.py new file mode 100644 index 0000000..1a64924 --- /dev/null +++ b/src/bf_pktpy/library/specs/templates/cpu/mod_header.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python + + +# Copyright (c) 2022 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" ModHeader template """ +from bf_pktpy.library.specs.packet import Packet +from bf_pktpy.library.fields import ByteField, ShortField, IntField + + +class ModHeader(Packet): + name = "ModHeader" + fields_desc = [ + IntField("switch_id", 0), + ShortField("ingress_port", 0), + ShortField("egress_port", 0), + ByteField("queue_id", 0), + ByteField("drop_reason", 0), + ShortField("pad", 0), + ] + + def _combine(self, body_copy): + if body_copy.name == "Ether": + self._body = body_copy + return self + + raise ValueError("Unsupported binding") diff --git a/src/bf_pktpy/library/specs/templates/cpu/postcard_header.py b/src/bf_pktpy/library/specs/templates/cpu/postcard_header.py new file mode 100644 index 0000000..ef8c127 --- /dev/null +++ b/src/bf_pktpy/library/specs/templates/cpu/postcard_header.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python + + +# Copyright (c) 2022 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" PostcardHeader template """ +from bf_pktpy.library.specs.packet import Packet +from bf_pktpy.library.fields import ByteField, ShortField, ThreeBytesField, IntField + + +class PostcardHeader(Packet): + name = "PostcardHeader" + fields_desc = [ + IntField("switch_id", 0), + ShortField("ingress_port", 0), + ShortField("egress_port", 0), + ByteField("queue_id", 0), + ThreeBytesField("queue_depth", 0), + IntField("egress_tstamp", 0), + ] + + def _combine(self, body_copy): + if body_copy.name == "Ether": + self._body = body_copy + return self + + raise ValueError("Unsupported binding") diff --git a/src/bf_pktpy/library/specs/templates/cpu/simple_l3_mirror_cpu_header.py b/src/bf_pktpy/library/specs/templates/cpu/simple_l3_mirror_cpu_header.py new file mode 100644 index 0000000..9833904 --- /dev/null +++ b/src/bf_pktpy/library/specs/templates/cpu/simple_l3_mirror_cpu_header.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python + + +# Copyright (c) 2022 Intel Corporation +# All Rights Reserved. +# +# This software and the related documents are Intel copyrighted materials, +# and your use of them is governed by the express license under which they +# were provided to you ("License"). Unless the License provides otherwise, +# you may not use, modify, copy, publish, distribute, disclose or transmit this +# software or the related documents without Intel's prior written permission. +# +# This software and the related documents are provided as is, with no express or +# implied warranties, other than those that are expressly stated in the License. + +############################################################################### +""" SimpleL3SwitchCpuHeader template """ +import six +from bf_pktpy.library.specs.packet import Packet +from bf_pktpy.library.fields import ShortEnumField, ShortField + + +class SimpleL3SwitchCpuHeader(Packet): + MIRROR_TYPE_MAPPING = {"ingress": 0, "egress": 1} + MIRROR_TYPE_REVERSED = { + value: key for key, value in six.iteritems(MIRROR_TYPE_MAPPING) + } + + name = "SimpleL3SwitchCpuHeader" + fields_desc = [ + ShortEnumField("mirror_type", 0, MIRROR_TYPE_REVERSED), + ShortField("ingress_port", 0), + ShortField("pkt_length", 0), + ] + + def __init__(self, **kwargs): + mirror = kwargs.pop("mirror_type", 0) + if isinstance(mirror, six.string_types): + if mirror.lower() not in self.MIRROR_TYPE_MAPPING: + raise ValueError( + "Mirror type can be only either 'Ingress' or " "'Egress'" + ) + mirror = self.MIRROR_TYPE_MAPPING[mirror.lower()] + kwargs["mirror_type"] = mirror + + super(SimpleL3SwitchCpuHeader, self).__init__(**kwargs) + + def _combine(self, body_copy): + if body_copy.name == "Ether": + self._body = body_copy + return self + + raise ValueError("Unsupported binding") diff --git a/src/bf_pktpy/library/specs/templates/dhcp.py b/src/bf_pktpy/library/specs/templates/dhcp.py new file mode 100644 index 0000000..78f95a7 --- /dev/null +++ b/src/bf_pktpy/library/specs/templates/dhcp.py @@ -0,0 +1,224 @@ +#!/usr/bin/env python + + +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" DHCP template """ +from bf_pktpy.library.specs.base import Base +import ipaddress + + +# ============================================================================= +_options = { + "pad": (0, 0, ""), + "subnet_mask": (1, 4, "0.0.0.0"), + "time_zone": (2, 4, 500), + "router": (3, 4, "0.0.0.0"), + "time_server": (4, 4, "0.0.0.0"), + "IEN_name_server": (5, 4, "0.0.0.0"), + "name_server": (6, 4, "0.0.0.0"), + "log_server": (7, 4, "0.0.0.0"), + "cookie_server": (8, 4, "0.0.0.0"), + "lpr_server": (9, 4, "0.0.0.0"), + "impress-servers": (10, 4, "0.0.0.0"), + "resource-location-servers": (11, 4, "0.0.0.0"), + "hostname": (12, -1, ""), + "boot-size": (13, 4, 1000), + "dump_path": (14, -1, ""), + "domain": (15, -1, ""), + "swap-server": (16, 4, "0.0.0.0"), + "root_disk_path": (17, -1, ""), + "extensions-path": (18, -1, ""), + "ip-forwarding": (19, 4, 0), + "non-local-source-routing": (20, 4, 0), + "policy-filter": (21, 4, "0.0.0.0"), + "max_dgram_reass_size": (22, 4, 300), + "default_ttl": (23, 4, 50), + "pmtu_timeout": (24, 4, 1000), + "path-mtu-plateau-table": (25, 4, 1000), + "interface-mtu": (26, 4, 50), + "all-subnets-local": (27, 4, 0), + "broadcast_address": (28, 4, "0.0.0.0"), + "perform-mask-discovery": (29, 4, 0), + "mask-supplier": (30, 4, 0), + "router-discovery": (31, 4, 0), + "router-solicitation-address": (32, 4, "0.0.0.0"), + "static-routes": (33, 4, "0.0.0.0"), + "trailer-encapsulation": (34, 4, 0), + "arp_cache_timeout": (35, 4, 1000), + "ieee802-3-encapsulation": (36, 4, 0), + "tcp_ttl": (37, 4, 100), + "tcp_keepalive_interval": (38, 4, 1000), + "tcp_keepalive_garbage": (39, 4, 0), + "NIS_domain": (40, -1, "www.example.com"), + "NIS_server": (41, 4, "0.0.0.0"), + "NTP_server": (42, 4, "0.0.0.0"), + "vendor_specific": (43, -1, ""), + "NetBIOS_server": (44, 4, "0.0.0.0"), + "NetBIOS_dist_server": (45, 4, "0.0.0.0"), + "NETBIOS_node_type": (46, 4, 100), + "netbios-scope": (47, -1, ""), + "font-servers": (48, 4, "0.0.0.0"), + "x-display-manager": (49, 4, "0.0.0.0"), + "requested_addr": (50, 4, "0.0.0.0"), + "lease_time": (51, 4, 43200), + "dhcp-option-overload": (52, 4, 100), + "message-type": (53, 1, "_TYPES_"), + "server_id": (54, 4, "0.0.0.0"), + "param_req_list": (55, 4, ""), + "error_message": (56, -1, ""), + "max_dhcp_size": (57, 4, 1500), + "renewal_time": (58, 4, 21600), + "rebinding_time": (59, 4, 37800), + "vendor_class_id": (60, -1, "id"), + "client_id": (61, 7, ""), + "nwip-domain-name": (62, -1, ""), + "NISplus_domain": (64, -1, ""), + "NISplus_server": (65, 4, "0.0.0.0"), + "boot-file-name": (67, -1, ""), + "mobile-ip-home-agent": (68, 4, "0.0.0.0"), + "SMTP_server": (69, 4, "0.0.0.0"), + "POP3_server": (70, 4, "0.0.0.0"), + "NNTP_server": (71, 4, "0.0.0.0"), + "WWW_server": (72, 4, "0.0.0.0"), + "Finger_server": (73, 4, "0.0.0.0"), + "IRC_server": (74, 4, "0.0.0.0"), + "StreetTalk_server": (75, 4, "0.0.0.0"), + "StreetTalk_Dir_Assistance": (76, 4, "0.0.0.0"), + "slp_service_agent": (78, -1, ""), + "slp_service_scope": (79, -1, ""), + "client_FQDN": (81, -1, ""), + "relay_agent_information": (82, -1, ""), + "nds-server": (85, 4, "0.0.0.0"), + "nds-tree-name": (86, -1, ""), + "nds-context": (87, -1, ""), + "bcms-controller-namesi": (88, -1, ""), + "bcms-controller-address": (89, 4, "0.0.0.0"), + "client-last-transaction-time": (91, -1, 1000), + "associated-ip": (92, 4, "0.0.0.0"), + "pxe_client_architecture": (93, -1, ""), + "pxe_client_network_interface": (94, -1, ""), + "pxe_client_machine_identifier": (97, -1, ""), + "uap-servers": (98, -1, ""), + "pcode": (100, -1, ""), + "tcode": (101, -1, ""), + "netinfo-server-address": (112, 4, "0.0.0.0"), + "netinfo-server-tag": (113, -1, ""), + "default-url": (114, -1, ""), + "auto-config": (116, 4, 0), + "name-service-search": (117, 4, 0), + "subnet-selection": (118, 4, "0.0.0.0"), + "vendor_class": (124, -1, ""), + "vendor_specific_information": (125, -1, ""), + "pana-agent": (136, 4, "0.0.0.0"), + "v4-lost": (137, -1, ""), + "capwap-ac-v4": (138, 4, "0.0.0.0"), + "sip_ua_service_domains": (141, -1, ""), + "rdnss-selection": (146, -1, ""), + "v4-portparams": (159, -1, ""), + "v4-captive-portal": (160, -1, ""), + "pxelinux_magic": (208, -1, ""), + "pxelinux_configuration_file": (209, -1, ""), + "pxelinux_path_prefix": (210, -1, ""), + "pxelinux_reboot_time": (211, -1, ""), + "option-6rd": (212, -1, ""), + "v4-access-domain": (213, -1, ""), + "end": (255, -1, -1), +} + +_types = { + "discover": 1, + "offer": 2, + "request": 3, + "decline": 4, + "ack": 5, + "nak": 6, + "release": 7, + "inform": 8, + "force_renew": 9, + "lease_query": 10, + "lease_unassigned": 11, + "lease_unknown": 12, + "lease_active": 13, +} + + +class DHCP(Base): + """DHCP class + Definition: + DHCP + options (int) + Examples: + | + create + | dhcp = DHCP(options=..) + | + """ + + name = "DHCP" + + def __init__(self, **kwargs): + Base._prepare_kwargs(kwargs) + + super(DHCP, self).__init__() + opts = kwargs.pop("options") + key, value = "", "" + self.options = "" + if opts and isinstance(opts, list): + for opt in opts: + if isinstance(opt, tuple) and len(opt) == 2: + key, value = opt + elif isinstance(opt, str): + key = opt + else: + raise ValueError("Invalid type %r", type(opt)) + opt_val, opt_len, opt_def = _options.get(key) + hex_str = hex(opt_val)[2:] + if hex_str == "ff": + self.options += hex_str + continue + if opt_def == "_TYPES_": + len_ = hex(opt_len)[2:].zfill(2) + type_ = _types.get(value) + hex_str += len_ + hex(type_)[2:].zfill(2) + else: + if opt_len == -1: + len_ = hex(len(value))[2:] + len_ = len_.zfill(len(len_) + len(len_) % 2) + else: + len_ = hex(opt_len)[2:].zfill(2) + if opt_len == 7 and ":" in value: + hex_str += len_ + "01" + value.replace(":", "") + else: + len_ = hex(opt_len)[2:].zfill(2) + if isinstance(value, str) and "." in value: + # ipv4 address + value = int(ipaddress.IPv4Address(value)) + value = hex(value)[2:].zfill(8) + else: + value = hex(value)[2:] + hex_str += len_ + value + self.options += hex_str + + if kwargs: + raise ValueError("Unsupported key(s) %s" % list(kwargs.keys())) + + def _members(self): + """Member information""" + value = eval("0x" + self.options) + len_ = len(self.options) + members = (("options", value, len_),) + return {"dhcp": members} + + +# ============================================================================= diff --git a/src/bf_pktpy/library/specs/templates/dot1ad.py b/src/bf_pktpy/library/specs/templates/dot1ad.py new file mode 100644 index 0000000..639abad --- /dev/null +++ b/src/bf_pktpy/library/specs/templates/dot1ad.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python + + +# Copyright (c) 2022 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" Dot1AD template """ + +from bf_pktpy.library.specs.templates.dot1q import Dot1Q + + +class Dot1AD(Dot1Q): + """Dot1AD class + Definition: + Dot1AD + type (int) + prio (int) + id (int) + vlan (int) + Examples: + | + create + | dot1ad = Dot1AD(type=.., prio=.., ...) + | + make change + | dot1ad.type = + | dot1ad.prio = + | ... + """ + + name = "Dot1AD" + + def _combine(self, body_copy): + if body_copy.name == "Dot1AD": + self._body = body_copy + self.type = 0x88A8 + return self + return super(Dot1AD, self)._combine(body_copy) diff --git a/src/bf_pktpy/library/specs/templates/dot1q.py b/src/bf_pktpy/library/specs/templates/dot1q.py new file mode 100644 index 0000000..e4e3ea9 --- /dev/null +++ b/src/bf_pktpy/library/specs/templates/dot1q.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python + + +# Copyright (c) 2022 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" Dot1Q template """ + +from bf_pktpy.library.fields import BitField, XShortEnumField +from bf_pktpy.library.helpers.ether_types import ETYPES +from bf_pktpy.library.specs.packet import Packet + + +class Dot1Q(Packet): + """Dot1Q class + Definition: + Dot1Q + type (int) + prio (int) + id (int) + vlan (int) + Examples: + | + create + | dot1q = Dot1Q(type=.., prio=.., ...) + | + make change + | dot1q.type = + | dot1q.prio = + | ... + """ + + name = "Dot1Q" + fields_desc = [ + BitField("prio", 0, 3), + BitField("id", 0, 1), + BitField("vlan", 1, 12), + XShortEnumField("type", 0, ETYPES), + ] + + def _combine(self, body_copy): + if body_copy.name == "Dot1Q": + self._body = body_copy + self.type = 0x8100 + return self + if body_copy.name == "IP": + self._body = body_copy + self.type = 0x0800 + return self + if body_copy.name == "IPv6": + self._body = body_copy + self.type = 0x86DD + return self + if body_copy.name == "ARP": + self._body = body_copy + self.type = 0x0806 + return self + if body_copy.name == "MPLS": + self._body = body_copy + self._type = 0x8847 + return self + if body_copy.name == "ERSPAN_III": + self._body = body_copy + self._body.o = 0 + return self + if body_copy.name == "FabricHeader": + self._body = body_copy + self._type = 0x9000 + return self + + raise ValueError("Unsupported binding") diff --git a/src/bf_pktpy/library/specs/templates/erspan/__init__.py b/src/bf_pktpy/library/specs/templates/erspan/__init__.py new file mode 100644 index 0000000..a3605c4 --- /dev/null +++ b/src/bf_pktpy/library/specs/templates/erspan/__init__.py @@ -0,0 +1,18 @@ +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from bf_pktpy.library.specs.templates.erspan.erspan import ERSPAN +from bf_pktpy.library.specs.templates.erspan.erspan_ii import ERSPAN_II +from bf_pktpy.library.specs.templates.erspan.erspan_iii import ERSPAN_III +from bf_pktpy.library.specs.templates.erspan.erspan_platform_specific import ( + ERSPAN_PlatformSpecific, +) diff --git a/src/bf_pktpy/library/specs/templates/erspan/alternative/__init__.py b/src/bf_pktpy/library/specs/templates/erspan/alternative/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/bf_pktpy/library/specs/templates/erspan/alternative/erspan.py b/src/bf_pktpy/library/specs/templates/erspan/alternative/erspan.py new file mode 100644 index 0000000..1010536 --- /dev/null +++ b/src/bf_pktpy/library/specs/templates/erspan/alternative/erspan.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python + + +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" ERSPAN template """ +from bf_pktpy.library.specs.base import Base +from bf_pktpy.library.specs.validate import ToBeBitField, ToBeIntegerField + + +# ============================================================================= +class ERSPAN(Base): + """ + ERSPAN class + + ERSPAN + version (int) + vlan (int) + priority (int) + unknown2 (int) + direction (int) + truncated (int) + span_id (int) + unknown7 (int) + + Examples: + | + create + | erspan = ERSPAN(version=.., vlan=.., ..) + | + make change + | erspan.version = + | erspan.vlan = + | ... + """ + + name = "ERSPAN" + + version = ToBeBitField(4, default=1) + vlan = ToBeBitField(12) + priority = ToBeBitField(3) + unknown2 = ToBeBitField(1) + direction = ToBeBitField(1) + truncated = ToBeBitField(1) + span_id = ToBeBitField(10) + unknown7 = ToBeIntegerField() + + def __init__(self, **kwargs): + super(ERSPAN, self).__init__() + self.alternative = True + + self.version = kwargs.pop("version", 1) + self.vlan = kwargs.pop("vlan", 0) + self.priority = kwargs.pop("priority", 0) + self.unknown2 = kwargs.pop("unknown2", 0) + self.direction = kwargs.pop("direction", 0) + self.truncated = kwargs.pop("truncated", 0) + self.span_id = kwargs.pop("span_id", 0) + self.unknown7 = kwargs.pop("unknown7", 0) + + if kwargs: + raise ValueError("Unsupported key(s) %s" % kwargs.keys()) + + def _combine(self, body): + if hasattr(body, "name"): + last = self.get_last() + if last.name == "ERSPAN": + if body.name == "Ether": + last._body = body + return self + try: + self._body / body + return self + except ValueError: + raise ValueError("Unsupported value type") + + def _members(self): + """Members information""" + members = ( + ("version", self.version, 4), + ("vlan", self.vlan, 12), + ("priority", self.priority, 3), + ("unknown2", self.unknown2, 1), + ("direction", self.direction, 1), + ("truncated", self.truncated, 1), + ("span_id", self.span_id, 10), + ("unknown7", self.unknown7, 32), + ) + return {"erspan": members} + + +# ============================================================================= diff --git a/src/bf_pktpy/library/specs/templates/erspan/alternative/erspan_iii.py b/src/bf_pktpy/library/specs/templates/erspan/alternative/erspan_iii.py new file mode 100644 index 0000000..182cf46 --- /dev/null +++ b/src/bf_pktpy/library/specs/templates/erspan/alternative/erspan_iii.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python + + +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" ERSPAN_III template """ +from bf_pktpy.library.specs.base import Base +from bf_pktpy.library.specs.validate import ToBeBitField, ToBeIntegerField + + +# ============================================================================= +class ERSPAN_III(Base): + """ + ERSPAN_III class + + ERSPAN_III + version (int) + vlan (int) + priority (int) + unknown2 (int) + direction (int) + truncated (int) + span_id (int) + timestamp (int) + sgt_other (int) + + Examples: + | + create + | erspan_iii = ERSPAN_III(version=.., vlan=.., ..) + | + make change + | erspan_iii.version = + | erspan_iii.vlan = + | ... + """ + + name = "ERSPAN_III" + + version = ToBeBitField(4, default=2) + vlan = ToBeBitField(12) + priority = ToBeBitField(3) + unknown2 = ToBeBitField(1) + direction = ToBeBitField(1) + truncated = ToBeBitField(1) + span_id = ToBeBitField(10) + timestamp = ToBeIntegerField() + sgt_other = ToBeIntegerField() + + def __init__(self, **kwargs): + super(ERSPAN_III, self).__init__() + self.alternative = True + + self.version = kwargs.pop("version", 2) + self.vlan = kwargs.pop("vlan", 0) + self.priority = kwargs.pop("priority", 0) + self.unknown2 = kwargs.pop("unknown2", 0) + self.direction = kwargs.pop("direction", 0) + self.truncated = kwargs.pop("truncated", 0) + self.span_id = kwargs.pop("span_id", 0) + self.timestamp = kwargs.pop("timestamp", 0) + self.sgt_other = kwargs.pop("sgt_other", 0) + + if kwargs: + raise ValueError("Unsupported key(s) %s" % kwargs.keys()) + + def _combine(self, body): + if hasattr(body, "name"): + last = self.get_last() + if last.name == "ERSPAN_III": + if body.name == "Ether": + last._body = body + return self + if body.name == "PlatformSpecific": + last._body = body + return self + try: + self._body / body + return self + except ValueError: + raise ValueError("Unsupported value type") + + def _members(self): + """Members information""" + members = ( + ("version", self.version, 4), + ("vlan", self.vlan, 12), + ("priority", self.priority, 3), + ("unknown2", self.unknown2, 1), + ("direction", self.direction, 1), + ("truncated", self.truncated, 1), + ("span_id", self.span_id, 10), + ("timestamp", self.timestamp, 32), + ("sgt_other", self.sgt_other, 32), + ) + return {"erspan_iii": members} + + +# ============================================================================= diff --git a/src/bf_pktpy/library/specs/templates/erspan/alternative/platform_specific.py b/src/bf_pktpy/library/specs/templates/erspan/alternative/platform_specific.py new file mode 100644 index 0000000..fabbcd7 --- /dev/null +++ b/src/bf_pktpy/library/specs/templates/erspan/alternative/platform_specific.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python + + +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" PlatformSpecific template """ +from bf_pktpy.library.specs.base import Base +from bf_pktpy.library.specs.validate import ToBeBitField, ToBeIntegerField + + +# ============================================================================= +class PlatformSpecific(Base): + """ + PlatformSpecific class + + PlatformSpecific + platf_id (int) + info1 (int) + info2 (int) + + Examples: + | + create + | platform_specific = PlatformSpecific(platf_id=.., info1=.., .) + | + make change + | platform_specific.platf_id = + | platform_specific.info1 = + | ... + """ + + name = "PlatformSpecific" + + platf_id = ToBeBitField(6) + info1 = ToBeBitField(26) + info2 = ToBeIntegerField() + + def __init__(self, **kwargs): + super(PlatformSpecific, self).__init__() + self.alternative = True + + self.platf_id = kwargs.pop("platf_id", 0) + self.info1 = kwargs.pop("info1", 0) + self.info2 = kwargs.pop("info2", 0) + + if kwargs: + raise ValueError("Unsupported key(s) %s" % kwargs.keys()) + + def _combine(self, body): + if hasattr(body, "name"): + last = self.get_last() + if last.name == "PlatformSpecific": + if body.name == "Ether": + last._body = body + return self + try: + self._body / body + return self + except ValueError: + raise ValueError("Unsupported value type") + + def _members(self): + """Members information""" + members = ( + ("platf_id", self.platf_id, 6), + ("info1", self.info1, 26), + ("info2", self.info2, 32), + ) + return {"platform_specific": members} + + +# ============================================================================= diff --git a/src/bf_pktpy/library/specs/templates/erspan/erspan.py b/src/bf_pktpy/library/specs/templates/erspan/erspan.py new file mode 100644 index 0000000..e30aded --- /dev/null +++ b/src/bf_pktpy/library/specs/templates/erspan/erspan.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python + + +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" ERSPAN template """ +from bf_pktpy.library.specs.templates.erspan.erspan_ii import ERSPAN_II + + +# ============================================================================= +class ERSPAN(ERSPAN_II): + """ + This is a dummy class to set a ERSPAN II as a default + """ + + name = "ERSPAN" + + +# ============================================================================= diff --git a/src/bf_pktpy/library/specs/templates/erspan/erspan_ii.py b/src/bf_pktpy/library/specs/templates/erspan/erspan_ii.py new file mode 100644 index 0000000..7f0fcbe --- /dev/null +++ b/src/bf_pktpy/library/specs/templates/erspan/erspan_ii.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python + + +# Copyright (c) 2022 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" ERSPAN_II template """ +from bf_pktpy.library.specs.packet import Packet +from bf_pktpy.library.fields import BitField + +# ============================================================================= + + +class ERSPAN_II(Packet): + """ + ERSPAN_II class + + ERSPAN_II + ver (int) + vlan (int) + cos (int) + en (int) + t (int) + session_id (int) + reserved (int) + index (int) + + Examples: + | + create + | erspan_ii = ERSPAN_II(ver=.., vlan=.., ..) + | + make change + | erspan_ii.ver = + | erspan_ii.vlan = + | ... + """ + + name = "ERSPAN_II" + 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), + ] + + def _combine(self, body_copy): + if body_copy.name == "Ether": + self._body = body_copy + return self + + raise ValueError("Unsupported binding") + + +# ============================================================================= diff --git a/src/bf_pktpy/library/specs/templates/erspan/erspan_iii.py b/src/bf_pktpy/library/specs/templates/erspan/erspan_iii.py new file mode 100644 index 0000000..89be006 --- /dev/null +++ b/src/bf_pktpy/library/specs/templates/erspan/erspan_iii.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python + + +# Copyright (c) 2022 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" ERSPAN_III template """ +from bf_pktpy.library.specs.packet import Packet +from bf_pktpy.library.fields import BitField, XIntField, XShortField, BitEnumField + +# ============================================================================= + + +class ERSPAN_III(Packet): + """ + ERSPAN_III class + + ERSPAN_III + ver (int) + vlan (int) + cos (int) + bso (int) + t (int) + session_id (int) + timestamp (int) + sgt_other (int) + p (int) + ft (int) + hw (int) + d (int) + gra (int) + o (int) + + Examples: + | + create + | erspan_iii = ERSPAN_III(ver=.., vlan=.., ..) + | + make change + | erspan_iii.ver = + | erspan_iii.vlan = + | ... + """ + + name = "ERSPAN_III" + 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), + ] + + def _combine(self, body_copy): + if body_copy.name == "Ether": + self.o = 0 + self._body = body_copy + return self + if body_copy.name == "ERSPAN_PlatformSpecific": + self.o = 1 + self._body = body_copy + return self + + raise ValueError("Unsupported binding") + + +# ============================================================================= diff --git a/src/bf_pktpy/library/specs/templates/erspan/erspan_platform_specific.py b/src/bf_pktpy/library/specs/templates/erspan/erspan_platform_specific.py new file mode 100644 index 0000000..35aac31 --- /dev/null +++ b/src/bf_pktpy/library/specs/templates/erspan/erspan_platform_specific.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python + + +# Copyright (c) 2022 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" ERSPAN_OLD Platform Specific template """ +from bf_pktpy.library.specs.packet import Packet +from bf_pktpy.library.fields import BitField, XIntField + +# ============================================================================= + + +class ERSPAN_PlatformSpecific(Packet): + """ + ERSPAN_PlatformSpecific class + + ERSPAN_PlatformSpecific + platf_if (int) + info1 (int) + info2 (int) + + Examples: + | + create + | erspan_platform_specific = + | ERSPAN_PlatformSpecific(platf_if=.., info1=.., ..) + | + make change + | erspan_platform_specific.info1 = + | ... + """ + + name = "ERSPAN_PlatformSpecific" + fields_desc = [ + BitField("platf_id", 0, 6), + BitField("info1", 0, 26), + XIntField("info2", 0x00000000), + ] + + def _combine(self, body_copy): + if body_copy.name == "Ether": + self._body = body_copy + return self + + raise ValueError("Unsupported binding") + + +# ============================================================================= diff --git a/src/bf_pktpy/library/specs/templates/ethernet.py b/src/bf_pktpy/library/specs/templates/ethernet.py new file mode 100644 index 0000000..3a3ab19 --- /dev/null +++ b/src/bf_pktpy/library/specs/templates/ethernet.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python + + +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" Ether template """ +from bf_pktpy.library.helpers.ether_types import ETYPES +from bf_pktpy.library.specs.packet import Packet +from bf_pktpy.library.fields import SourceMACField, DestMACField, XShortEnumField + + +class Ether(Packet): + """Ether class + + Definition: + Ether + src (str) + dst (str) + type (int) + + Examples: + | + create + | ethernet = Ether(src=.., dst=.., type=..) + | + make change + | ethernet.src = + | ethernet.dst = + | ethernet.type = + """ + + name = "Ether" + fields_desc = [ + DestMACField("dst"), + SourceMACField("src"), + XShortEnumField("type", 0x9000, ETYPES), + ] + + @property + def type_name(self): + """Provides human-readable type of packet based on raw value. + + :return: human-readable type of packet + :rtype: str + """ + return ETYPES.get(hex(self.type)[2:].zfill(4)) + + def _combine(self, body_copy): + if body_copy.name == "Dot1AD": + self._body = body_copy + self.type = 0x88A8 + return self + if body_copy.name == "Dot1Q": + self._body = body_copy + self.type = 0x8100 + return self + if body_copy.name == "ARP": + self._body = body_copy + self.type = 0x0806 + return self + if body_copy.name == "IP": + self._body = body_copy + self.type = 0x0800 + return self + if body_copy.name == "IPv6": + self._body = body_copy + self.type = 0x86DD + return self + if body_copy.name == "MPLS": + self._body = body_copy + self.type = 0x8847 + return self + if body_copy.name == "ERSPAN_III": + self._body = body_copy + if not body_copy.alternative: + self._body.o = 0 + return self + if body_copy.name == "FabricHeader": + self._body = body_copy + self.type = 0x9000 + return self + if body_copy.name == "SimpleL3SwitchCpuHeader": + self._body = body_copy + self.type = 0xBF01 + return self + if body_copy.name == "MACControlClassBasedFlowControl": + self._body = body_copy + self.type = 0x8808 + return self + + raise ValueError("Unsupported binding") + + @classmethod + def from_hex(cls, hex_tr): + """Create object from hex value""" + if ETYPES.get(hex_tr[24:28]): + dst = ":".join(hex_tr[i : i + 2] for i in range(0, 12, 2)) + src = ":".join(hex_tr[i : i + 2] for i in range(12, 24, 2)) + type_ = int(hex_tr[24:28], 16) + return Ether(dst=dst, src=src, type=type_) + return None diff --git a/src/bf_pktpy/library/specs/templates/frame.py b/src/bf_pktpy/library/specs/templates/frame.py new file mode 100644 index 0000000..2cb6d29 --- /dev/null +++ b/src/bf_pktpy/library/specs/templates/frame.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python + + +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" Frame template """ +from bf_pktpy.library.specs.base import Base + + +# ============================================================================= +class Frame(Base): + """Frame class + + Frame + sizes (list) + + Examples: + | + create + | frame = Frame(sizes=[256]) + | + make change + | frame.sizes = [256] + """ + + def __init__(self, sizes=None): + self.sizes = sizes or [512] + + def _members(self): + """Member information""" + members = (("sizes", self.sizes),) + return {"frame": members} + + +# ============================================================================= diff --git a/src/bf_pktpy/library/specs/templates/gre.py b/src/bf_pktpy/library/specs/templates/gre.py new file mode 100644 index 0000000..d674435 --- /dev/null +++ b/src/bf_pktpy/library/specs/templates/gre.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python + + +# Copyright (c) 2022 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" GRE template """ +from bf_pktpy.library.helpers.bin import to_bin +from bf_pktpy.library.helpers.chksum import checksum +from bf_pktpy.library.helpers.ether_types import ETYPES +from bf_pktpy.library.specs.packet import Packet +from bf_pktpy.library.fields import ( + BitField, + ConditionalField, + XShortField, + XIntField, + XShortEnumField, +) + + +def gre_checksum(packet): + """Calculate GRE checksum""" + # noinspection PyProtectedMember + members = packet._members(chksum=0) + binary = "" + for _, value, size in members[packet.name]: + binary += to_bin(value, size) + if len(binary) % 16 > 0: + binary += "00000000" + return checksum(binary) + + +class GRE(Packet): + name = "GRE" + 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, ETYPES), + ConditionalField( + XShortField("chksum", gre_checksum), + lambda pkt: pkt.chksum_present == 1 or pkt.routing_present == 1, + ), + ConditionalField( + XShortField("offset", 0), + lambda pkt: pkt.chksum_present == 1 or pkt.routing_present == 1, + ), + ConditionalField(XIntField("key", 0), lambda pkt: pkt.key_present == 1), + ConditionalField( + XIntField("sequence_number", 0), lambda pkt: pkt.seqnum_present == 1 + ), + ] + + def _combine(self, body_copy): + if body_copy.name == "Ether": + self.proto = 0x6558 + self._body = body_copy + return self + if body_copy.name == "Dot1AD": + self.proto = 0x88A8 + self._body = body_copy + return self + if body_copy.name == "Dot1Q": + self.proto = 0x8100 + self._body = body_copy + return self + if body_copy.name == "IPv6": + self.proto = 0x86DD + self._body = body_copy + return self + if body_copy.name == "IP": + self.proto = 0x0800 + self._body = body_copy + return self + if body_copy.name == "MPLS": + self.proto = 0x8847 + self._body = body_copy + return self + if body_copy.name == "ERSPAN": + self.proto = 0x88BE + self._body = body_copy + return self + if body_copy.name in ("ERSPAN", "ERSPAN_II"): + self.proto = 0x88BE + self._body = body_copy + self.seqnum_present = 1 + return self + if body_copy.name == "ERSPAN_III": + self.proto = 0x22EB + self._body = body_copy + return self + + raise ValueError("Unsupported binding") + + @property + def type_name(self): + """Provides human-readable 'proto' value of the subsequent header. + + :return: human-readable type of packet + :rtype: str + """ + return ETYPES.get(hex(self.proto)[2:].zfill(4)) diff --git a/src/bf_pktpy/library/specs/templates/gtpu.py b/src/bf_pktpy/library/specs/templates/gtpu.py new file mode 100644 index 0000000..734eec2 --- /dev/null +++ b/src/bf_pktpy/library/specs/templates/gtpu.py @@ -0,0 +1,81 @@ +# Copyright (c) 2022 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +from bf_pktpy.library.specs.packet import Packet +from bf_pktpy.library.fields import ( + BitField, + ByteField, + XBitField, + ByteEnumField, + ShortField, + IntField, + ConditionalField, +) + + +GTPMessageType = { + 1: "Echo Request", + 2: "Echo Response", + 16: "Create PDP Context Request", + 17: "Create PDP Context Response", + 18: "Update PDP Context Request", + 19: "Update PDP Context Response", + 20: "Delete PDP Context Request", + 21: "Delete PDP Context Response", + 26: "Error Indication", + 27: "PDU Notification Request", + 28: "PDU Notification Response", + 31: "Supported Extension Headers Notification", + 254: "End Marker", + 255: "G-PDU", +} + +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", +} + + +def gtpu_flag_condition(packet): + return packet.e == 1 or packet.s == 1 or packet.pn == 1 + + +class GTPU(Packet): + + name = "GTPU" + fields_desc = [ + BitField("pn", 0, 1), + BitField("s", 0, 1), + BitField("e", 0, 1), + BitField("reserved", 0, 1), + BitField("pt", 0, 1), + BitField("version", 0, 3), + ByteEnumField("gtp_type", 0, GTPMessageType), + ShortField("length", 0), + IntField("teid", 0), + ConditionalField(XBitField("seq", 0, 16), gtpu_flag_condition), + ConditionalField(ByteField("npdu", 0), gtpu_flag_condition), + ConditionalField( + ByteEnumField("next_ex", 0, ExtensionHeadersTypes), gtpu_flag_condition + ), + ] + + @property + def message(self): + return GTPMessageType.get(self.gtp_type) + + @property + def next_extension_header(self): + return ExtensionHeadersTypes.get(self.next_ex) + + def _combine(self, body_copy): + if body_copy.name in ("IP", "IPv6"): + self._body = body_copy + return self + + raise ValueError("Unsupported binding") diff --git a/src/bf_pktpy/library/specs/templates/icmp.py b/src/bf_pktpy/library/specs/templates/icmp.py new file mode 100644 index 0000000..41f685f --- /dev/null +++ b/src/bf_pktpy/library/specs/templates/icmp.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python + + +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" ICMP template """ +from bf_pktpy.library.specs.base import Base + + +# ============================================================================= +class ICMP(Base): + """ICMP class + + ICMP + type (int) + code (int) + chksum (int) + id (int) + seq (int) + + Examples: + | + create + | icmp = ICMP(type=.., code=.., ..) + | + make change + | icmp.type = + | icmp.code = + | icmp.chksum = + | icmp.id = + | icmp.seq = + """ + + name = "ICMP" + + def __init__(self, **kwargs): + Base._prepare_kwargs(kwargs) + + super(ICMP, self).__init__() + self.type = kwargs.pop("type", 8) + self.code = kwargs.pop("code", 0) + self.chksum = kwargs.pop("chksum", 0) + self.id = kwargs.pop("id", 0) + self.seq = kwargs.pop("seq", 0) + if kwargs: + raise ValueError("Unsupported key(s) %s" % list(kwargs.keys())) + + @property + def hdr_len(self): + """Get header length""" + return 8 + + @property + def total_len(self): + """Get full length""" + if self._body: + return self.hdr_len + len(self._body) + return self.hdr_len + + @staticmethod + def from_hex(hex_str): + """Create object from hex value""" + type_ = int(hex_str[:2], 16) + code = int(hex_str[2:4], 16) + chksum = int(hex_str[4:8], 16) + id_ = int(hex_str[8:12], 16) + seq = int(hex_str[12:16], 16) + kwargs = {"type": type_, "code": code, "chksum": chksum, "id": id_, "seq": seq} + return ICMP(**kwargs) + + def _members(self): + """Member information""" + + members = ( + ("type", self.type, 8), + ("code", self.code, 8), + ("chksum", self.chksum, 16), + ("id", self.id, 16), + ("seq", self.seq, 16), + ) + return {"icmp": members} + + +# ============================================================================= diff --git a/src/bf_pktpy/library/specs/templates/icmpv6_unknown.py b/src/bf_pktpy/library/specs/templates/icmpv6_unknown.py new file mode 100644 index 0000000..db2eee7 --- /dev/null +++ b/src/bf_pktpy/library/specs/templates/icmpv6_unknown.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python + + +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" ICMPv6Unknown template """ +from bf_pktpy.library.specs.base import Base + + +# ============================================================================= +class ICMPv6Unknown(Base): + """ICMPv6Unknown fallback class + + ICMPv6Unknown + type (int) + code (int) + chksum (int) + msgbody (str) + + Examples: + | + create + | icmp = ICMPv6Unknown(type=.., code=.., ..) + | + make change + | icmp.type = + | icmp.code = + | icmp.chksum = + | icmp.msgbody = + """ + + name = "ICMPv6Unknown" + + def __init__(self, **kwargs): + Base._prepare_kwargs(kwargs) + + super(ICMPv6Unknown, self).__init__() + self.type = kwargs.pop("type", 8) + self.code = kwargs.pop("code", 0) + self.chksum = kwargs.pop("chksum", 0) + self.msgbody = kwargs.pop("msgbody", "") + if kwargs: + raise ValueError("Unsupported key(s) %s" % list(kwargs.keys())) + + def _members(self): + """Member information""" + + members = ( + ("type", self.type, 8), + ("code", self.code, 8), + ("chksum", self.chksum, 16), + ("msgbody", self.msgbody, 16), + ) + return {"icmpv6_unknown": members} + + +# ============================================================================= diff --git a/src/bf_pktpy/library/specs/templates/igmp.py b/src/bf_pktpy/library/specs/templates/igmp.py new file mode 100644 index 0000000..8f3737c --- /dev/null +++ b/src/bf_pktpy/library/specs/templates/igmp.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python + + +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" IGMP template """ +import ipaddress +import six + +from bf_pktpy.library.specs.base import Base + + +# ============================================================================= +class IGMP(Base): + """IGMP class + + IGMP + type (int) + mrcode (int) + chksum (int) + gaddr (int) + + + Examples: + | + create + | igmp = IGMP(type=.., mrcode=.., ..) + | + make change + | igmp.type = + | igmp.mrcode = + | igmp.chksum = + | igmp.gaddr = + """ + + name = "IGMP" + + def __init__(self, **kwargs): + Base._prepare_kwargs(kwargs) + + super(IGMP, self).__init__() + self.type = kwargs.pop("type", 17) + self.mrcode = kwargs.pop("mrcode", 20) + self.chksum = kwargs.pop("chksum", 0) + self.gaddr = kwargs.pop("gaddr", "0.0.0.0") + if kwargs: + raise ValueError("Unsupported key(s) %s" % list(kwargs.keys())) + + @property + def hdr_len(self): + """Get header length""" + return 8 + + @property + def total_len(self): + """Get full length""" + if self._body: + return self.hdr_len + len(self._body) + return self.hdr_len + + @staticmethod + def from_hex(hex_str): + """Create object from hex value""" + type = int(hex_str[:2], 16) + mrcode = int(hex_str[2:4], 16) + chksum = int(hex_str[4:8], 16) + gaddr = int(hex_str[8:16], 16) + gaddr = ".".join([str(int(gaddr[x : x + 2], 16)) for x in range(0, 8, 2)]) + kwargs = {"type": type, "mrcode": mrcode, "chksum": chksum, "gaddr": gaddr} + return IGMP(**kwargs) + + def _members(self): + """Member information""" + gaddr = int(ipaddress.IPv4Address(six.ensure_text(self.gaddr))) + members = ( + ("type", self.type, 8), + ("mrcode", self.mrcode, 8), + ("chksum", self.chksum, 16), + ("gaddr", gaddr, 32), + ) + return {"igmp": members} + + +# ============================================================================= diff --git a/src/bf_pktpy/library/specs/templates/ipoption.py b/src/bf_pktpy/library/specs/templates/ipoption.py new file mode 100644 index 0000000..f0a6e39 --- /dev/null +++ b/src/bf_pktpy/library/specs/templates/ipoption.py @@ -0,0 +1,226 @@ +#!/usr/bin/env python + + +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" IPOption templates """ +import six + +from bf_pktpy.library.specs.packet import Packet +from bf_pktpy.library.fields import ( + BitField, + ByteField, + ByteEnumField, + ShortField, + StrField, + IPListField, +) + +_option_names = { + 0: "EOOL", + 1: "NOP", + 130: "SEC", + 131: "LSR", + 68: "TS", + 133: "E-SEC", + 134: "CIPSO", + 7: "RR", + 136: "SID", + 137: "SSR", + 10: "ZSU", + 11: "MTUP", + 12: "MTUR", + 205: "FINN", + 142: "VISA", + 15: "ENCODE", + 144: "IMITD", + 145: "EIP", + 82: "TR", + 147: "ADDEXT", + 148: "RTRALT", + 149: "SDB", + 151: "DPS", + 152: "UMP", + 25: "QS", + 30: "EXP1", + 94: "EXP2", + 158: "EXP3", + 222: "EXP4", +} + +_opt_type_header = ByteEnumField("opt_type", 0, _option_names) + + +class _IPOption(Packet): + """Base class for IP options header definitions""" + + name = "Internal IP Option" + fields_desc = [_opt_type_header] + + def __init__(self, raw_option=None, **fields): + self.default_if_none = False + if raw_option is not None: + if not isinstance(raw_option, six.binary_type): + raw_option = six.ensure_binary(raw_option) + # we are omitting Packet constructor as we don't want to use **fields in + # this case + super(Packet, self).__init__() + for field_def in self.fields_desc: + object.__setattr__(self, field_def.name, None) + self.parse_raw_option(raw_option) + else: + self.default_if_none = True + super(_IPOption, self).__init__(**fields) + self.opt_type = next( + ( + num + for num, name in six.iteritems(_option_names) + if name == self.name.lstrip("IP Option") + ), + self.opt_type, + ) + + def _members(self, **fields_to_override): + return super(_IPOption, self)._members( + default_if_none=self.default_if_none, **fields_to_override + ) + + # noinspection PyAttributeOutsideInit + def parse_raw_option(self, raw_option): + option_bits = "".join(bin(opt_byte)[2:].zfill(8) for opt_byte in raw_option) + offset = 0 + for field in self.fields_desc: + if offset >= len(option_bits): + return + new_offset = offset + field.size + value = ( + int(option_bits[offset:new_offset], 2) + if option_bits[offset:new_offset] + else None + ) + object.__setattr__(self, field.name, value) + offset = new_offset + + if len(raw_option) > self.hdr_len: + self._body = raw_option[self.hdr_len :] + + +class IPOption(_IPOption): + """Default IP Option definition + + This type is for cases when byte sequence is + not recognized (does not match with any defined IP options). + """ + + name = "IP Option" + fields_desc = [ + _opt_type_header, + ByteField("length", lambda opt: 2 + len(opt.value)), + StrField("value", ""), + ] + + # noinspection PyMissingConstructor + def __init__(self, raw_option=None, **fields): + opt_class = self.__class__ + if raw_option is not None: + if not isinstance(raw_option, six.binary_type): + raw_option = six.ensure_binary(raw_option) + # We are guessing type of provided IP option, defaults to this class + opt_class = ipoptions_mapping.get(raw_option[0], IPOption) + self.__class__ = opt_class + + # In fact, we are calling _IPOption constructor + super(opt_class, self).__init__(raw_option, **fields) + + # noinspection PyAttributeOutsideInit + def parse_raw_option(self, raw_option): + try: + self.opt_type = raw_option[0] + self.length = raw_option[1] + self.value = raw_option[2:] + except IndexError: + return + + +class IPOption_EOL(_IPOption): + name = "IP Option EOOL" + fields_desc = [_opt_type_header] + + +class IPOption_NOP(_IPOption): + name = "IP Option NOP" + fields_desc = [_opt_type_header] + + +class IPOption_Stream_Id(_IPOption): + name = "IP Option SID" + fields_desc = [_opt_type_header, ByteField("length", 4), ShortField("security", 0)] + + +class IPOption_Security(_IPOption): + name = "IP Option SEC" + fields_desc = [ + _opt_type_header, + ByteField("length", 11), + ShortField("security", 0), + ShortField("compartment", 0), + ShortField("handling_restrictions", 0), + BitField("transmission_control_code", 0, 24), + ] + + +class _IPOption_RR(_IPOption): + name = "IP Option RR" + fields_desc = [ + _opt_type_header, + ByteField("length", lambda pkt: len(pkt.route_data) * 4 + 3), + ByteField("pointer", 4), + IPListField("route_data"), + ] + + # noinspection PyAttributeOutsideInit + def parse_raw_option(self, raw_option): + try: + self.opt_type = raw_option[0] + self.length = raw_option[1] + self.pointer = raw_option[2] + self.route_data = [] + ip_addr_count = (self.length - 3) // 4 + for i in range(ip_addr_count): + self.route_data.append(raw_option[3 + i * 4 : 3 + (i + 1) * 4]) + + except IndexError: + return + if len(raw_option) > 11: + self._body = raw_option[11:] + + +class IPOption_LSRR(_IPOption_RR): + name = "IP Option LSR" + + +class IPOption_SSRR(_IPOption_RR): + name = "IP Option SSR" + + +ipoptions_mapping = { + 0: IPOption_EOL, + 1: IPOption_NOP, + 136: IPOption_Stream_Id, + 130: IPOption_Security, + 131: IPOption_LSRR, + 137: IPOption_SSRR, +} + +IPOptionPlaceholder = IPOption diff --git a/src/bf_pktpy/library/specs/templates/ipv4.py b/src/bf_pktpy/library/specs/templates/ipv4.py new file mode 100644 index 0000000..40b9038 --- /dev/null +++ b/src/bf_pktpy/library/specs/templates/ipv4.py @@ -0,0 +1,287 @@ +#!/usr/bin/env python + + +# Copyright (c) 2022 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" IP template """ +import six + +from bf_pktpy.library.helpers.bin import to_bin +from bf_pktpy.library.helpers.chksum import checksum +from bf_pktpy.library.helpers.ip_types import ITYPES +from bf_pktpy.library.specs.packet import Packet +from bf_pktpy.library.fields import ( + BitField, + ByteField, + ShortField, + FlagsField, + XShortField, + XByteField, + ByteEnumField, + SourceIPField, + DestIPField, + IPOptionsListField, +) + + +def total_len(packet): + return packet.total_len + + +def calculate_ihl(packet): + return packet.hdr_len // 4 + + +def ipv4_checksum(packet): + """Calculate ipv4 checksum""" + # noinspection PyProtectedMember + members = packet._members(chksum=0) + binary = "" + for _, value, size in members[packet.name]: + binary += to_bin(value, size) + return checksum(binary) + + +class IP(Packet): + """IP class + + Definition: + IP + version (int) + ihl (int) + tos (int) + len (int) + id (int) + flags (int) + frag (int) + ttl (int) + proto (int) + chksum (int) + src (str) + dst (str) + options (str) + + Examples: + | + create + | ipv4 = IP(version=.., ihl=.., ) + | + make change + | ipv4.version = BB + | ipv4.ihl = + | ... + """ + + name = "IP" + fields_desc = [ + BitField("version", 4, 4), + BitField("ihl", calculate_ihl, 4), + XByteField("tos", 0), + ShortField("len", total_len), + ShortField("id", 1), + FlagsField("flags", 0, 3, ["MF", "DF", "evil"]), + BitField("frag", 0, 13), + ByteField("ttl", 64), + ByteEnumField("proto", 0, ITYPES), + XShortField("chksum", ipv4_checksum), + SourceIPField("src", "dst"), + DestIPField("dst", "127.0.0.1"), + IPOptionsListField("options", []), + ] + + @property + def proto_name(self): + return ITYPES.get(self.proto) + + def _combine(self, body_copy): + if body_copy.name == "TCP": + self._body = body_copy + self.proto = 6 + if not self._body.is_lock("chksum"): + self._body._chksum = self.l4_checksum() + return self + if body_copy.name == "UDP": + self._body = body_copy + self.proto = 17 + return self + if body_copy.name == "ICMP": + self._body = body_copy + self.proto = 1 + self._body.chksum = self.l4_checksum() + return self + if body_copy.name == "IGMP": + self._body = body_copy + self.proto = 2 + if self.internal_value("ttl") is None: + self.ttl = 1 + self.frag = 0 + self._body.chksum = self.l4_checksum() + return self + if body_copy.name == "GRE": + self._body = body_copy + self.proto = 47 + self._body.chksum = self.l4_checksum() + return self + if body_copy.name == "MPLS": + self._body = body_copy + self.proto = 137 + if body_copy.name == "IP": + self._body = body_copy + self.proto = 4 + return self + if body_copy.name == "IPv6": + self._body = body_copy + self.proto = 41 + return self + + raise ValueError("Unsupported binding") + + @property + def hdr_len(self): + """Get header size""" + if not self.options: + return 20 + + option_len = sum(len(bytes(option)) for option in self.options) + padding = option_len % 4 + if padding != 0: + option_len += 4 - padding + return 20 + option_len + + def reset_chksum(self): + self.chksum = None # noqa + + def l4_checksum(self): + """Calculate tcp checksum""" + + def reset_l4_chksum(): + if not self._body.is_lock("chksum", False): + if hasattr(self._body, "_chksum"): + self._body._chksum = 0 + else: + self._body.chksum = 0 + + def members_lookup(members, field_name): + return next( + (val, size) for name, val, size in members if name == field_name + ) + + binary = "" + if self._body.name == "TCP": + _members = self._members()["IP"] + src_ip = to_bin(*members_lookup(_members, "src")) + dst_ip = to_bin(*members_lookup(_members, "dst")) + l4_proto = to_bin(*members_lookup(_members, "proto")) + ttlen = self.total_len + temp = ttlen - self.hdr_len + l4_len = to_bin(temp, 16) + resved = "00000000" + pseudo = src_ip + dst_ip + l4_len + resved + l4_proto + reset_l4_chksum() + l4_bin = self._body.bin() + binary = pseudo + l4_bin + if len(binary) % 16 > 0: + binary += "00000000" + if self._body.name in ("ICMP", "GRE", "IGMP"): + reset_l4_chksum() + binary = self._body.bin() + if len(binary) % 16 > 0: + binary += "00000000" + return checksum(binary) + + def _post_build(self): + self.update_l4_checksum() + + # TODO(sborkows): To be modified when all L4 headers will be rewritten + def update_l4_checksum(self): + """Self update l4 checksum""" + if self.body is None: + return + + if isinstance(self._body, (six.binary_type, six.string_types)): + return + + if self._body.name not in ("TCP", "ICMP", "GRE", "IGMP"): + return + + if self._body.is_lock("chksum", False): + return + + if hasattr(self._body, "_chksum"): + self._body._chksum = self.l4_checksum() + elif hasattr(self._body, "chksum"): + self._body.chksum = self.l4_checksum() + + @staticmethod + def from_hex(hex_str): + """Create object from hex value""" + version = int(hex_str[:1], 16) + ihl = int(hex_str[1:2], 16) + tos = int(hex_str[2:4], 16) + len_ = int(hex_str[4:8], 16) + id_ = int(hex_str[8:12], 16) + _flags_and_frag_bin = bin(int(hex_str[12:16], 16))[2:].zfill(16) + flags = int(_flags_and_frag_bin[:3], 2) + frag = int(_flags_and_frag_bin[3:], 2) + ttl = int(hex_str[16:18], 16) + proto = int(hex_str[18:20], 16) + chksum = int(hex_str[20:24], 16) + src = hex_str[24:32] + src = ".".join([str(int(src[x : x + 2], 16)) for x in range(0, 8, 2)]) + dst = hex_str[32:40] + dst = ".".join([str(int(dst[x : x + 2], 16)) for x in range(0, 8, 2)]) + + if ihl > 5: + opt_end = ihl * 4 * 2 + pad_end = 0 + if opt_end % 8: + pad_end = 8 - len(opt_end) % 8 + opt_ = hex_str[40 : opt_end + pad_end] + options = "" + for idx in range(len(opt_)): + temp = bin(int(opt_[idx], 16)) + options += temp[2:] + # if options: + # pad = (32 - len(options) % 32) * "0" + # options += pad + + kwargs = { + "version": version, + "ihl": ihl, + "tos": tos, + "len": len_, + "id": id_, + "flags": flags, + "frag": frag, + "ttl": ttl, + "proto": proto, + "chksum": chksum, + "src": src, + "dst": dst, + "options": options, + } + else: + kwargs = { + "version": version, + "ihl": ihl, + "tos": tos, + "len": len_, + "id": id_, + "flags": flags, + "frag": frag, + "ttl": ttl, + "proto": proto, + "chksum": chksum, + "src": src, + "dst": dst, + } + return IP(**kwargs) diff --git a/src/bf_pktpy/library/specs/templates/ipv6.py b/src/bf_pktpy/library/specs/templates/ipv6.py new file mode 100644 index 0000000..e7b663f --- /dev/null +++ b/src/bf_pktpy/library/specs/templates/ipv6.py @@ -0,0 +1,234 @@ +#!/usr/bin/env python + + +# Copyright (c) 2022 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" IPv6 template """ +import ipaddress +import six + +from bf_pktpy.library.helpers.bin import to_bin +from bf_pktpy.library.helpers.chksum import checksum +from bf_pktpy.library.specs.base import Base + + +# ============================================================================= +from bf_pktpy.library.specs.validate_src_dst import ValidateSrcDst + + +class IPv6(Base, ValidateSrcDst): + """IPv6 class + + Definition: + IPv6 + version (int) + tc (int) + fl (int) + plen (int) + nh (int) + hlim (int) + src (str) + dst (str) + + Examples: + | + create + | ipv6 = IPv6(version=.., tc=.., ) + | + make change + | ipv6.version = + | ipv6.tc = + | ... + """ + + name = "IPv6" + + _src = "::1" + _dst = "::1" + + def __init__(self, **kwargs): + Base._prepare_kwargs(kwargs) + + super(IPv6, self).__init__() + self.version = kwargs.pop("version", 6) + self.tc = kwargs.pop("tc", 0) + self.fl = kwargs.pop("fl", 0) + self.plen = kwargs.pop("plen", 0) + self.nh = kwargs.pop("nh", 59) + self.hlim = kwargs.pop("hlim", 64) + self.src = kwargs.pop("src", "::1") + self.dst = kwargs.pop("dst", "::1") + if kwargs: + raise ValueError("Unsupported key(s) %s" % list(kwargs.keys())) + + def _combine(self, body_copy): + if body_copy.name == "TCP": + self._body = body_copy + self.nh = 6 + if not self._body.is_lock("chksum"): + self._body._chksum = self.l4_checksum() + return self + if body_copy.name == "UDP": + self._body = body_copy + self.nh = 17 + return self + if body_copy.name == "ICMP": + self._body = body_copy + self.nh = 1 + self._body.chksum = self.l4_checksum() + return self + if body_copy.name == "ICMPv6Unknown": + self._body = body_copy + self.nh = 58 + self._body.chksum = self.l4_checksum() + return self + if body_copy.name == "IP": + self._body = body_copy + self.nh = 4 + if not self._body.is_lock("chksum"): + self._body._chksum = self.l4_checksum() + return self + if body_copy.name == "MPLS": + self._body = body_copy + self.nh = 137 + return self + if body_copy.name == "GRE": + self._body = body_copy + self.nh = 47 + self._body.chksum = self.l4_checksum() + return self + if body_copy.name == "IPv6": + self._body = body_copy + self.nh = 41 + self._body.chksum = self.l4_checksum() + return self + if body_copy.name == "IPv6ExtHdrRouting": + self._body = body_copy + self.nh = 43 + return self + + raise ValueError("Unsupported binding") + + def l4_checksum(self): + """Calculate tcp checksum""" + binary = "" + if self._body.name == "TCP": + src = int(ipaddress.IPv6Address(six.ensure_text(self.src))) + dst = int(ipaddress.IPv6Address(six.ensure_text(self.dst))) + src_ip = to_bin(src, 128) + dst_ip = to_bin(dst, 128) + l4_proto = to_bin(self.nh, 8) + ttlen = self.total_len + temp = ttlen - self.hdr_len + l4_len = to_bin(temp, 16) + resved = "00000000" + pseudo = src_ip + dst_ip + l4_len + resved + l4_proto + if hasattr(self._body, "_chksum"): + self._body._chksum = 0 + else: + self._body.chksum = 0 + l4_bin = self._body.bin() + binary = pseudo + l4_bin + if len(binary) % 16 > 0: + binary += "00000000" + if self._body.name in ("ICMP", "GRE"): + self._body.chksum = 0 + binary = self._body.bin() + if len(binary) % 16 > 0: + binary += "00000000" + return checksum(binary) + + def _post_build(self): + self.update_l4_checksum() + + def update_l4_checksum(self): + """Self update l4 checksum""" + if self.body is None: + return + + if isinstance(self._body, (six.binary_type, six.string_types)): + return + + if self._body.name not in ("TCP", "ICMP", "GRE"): + return + + if self._body.is_lock("chksum", False): + return + + if hasattr(self._body, "_chksum"): + self._body._chksum = self.l4_checksum() + else: + self._body.chksum = self.l4_checksum() + + @staticmethod + def from_hex(hex_str): + """Create object from hex value""" + version = int(hex_str[:1], 16) + tc = int(hex_str[1:3], 16) + fl = int(hex_str[3:8], 16) + plen = int(hex_str[8:12], 16) + nh = int(hex_str[12:14], 16) + hlim = int(hex_str[14:16], 16) + src = int(hex_str[16:48], 16) + dst = int(hex_str[48:80], 16) + + kwargs = { + "version": version, + "tc": tc, + "fl": fl, + "plen": plen, + "nh": nh, + "hlim": hlim, + "src": src, + "dst": dst, + } + return IPv6(**kwargs) + + @property + def payload_len(self): + """Get payload length""" + plen = 0 + if hasattr(self._body, "total_len"): + plen = self._body.total_len + return plen + + @property + def hdr_len(self): + """Get header size""" + return 40 + + @property + def total_len(self): + """Get full length""" + return self.hdr_len + self.payload_len + + def _members(self): + """Member information""" + self.post_build() + + src = int(ipaddress.IPv6Address(six.ensure_text(self.src))) + dst = int(ipaddress.IPv6Address(six.ensure_text(self.dst))) + + members = ( + ("version", self.version, 4), + ("tc", self.tc, 8), + ("fl", self.fl, 20), + ("plen", self.payload_len, 16), + ("nh", self.nh, 8), + ("hlim", self.hlim, 8), + ("src", src, 128), + ("dst", dst, 128), + ) + return {"ipv6": members} + + +# ============================================================================= diff --git a/src/bf_pktpy/library/specs/templates/ipv6_ext_hdr_routing.py b/src/bf_pktpy/library/specs/templates/ipv6_ext_hdr_routing.py new file mode 100644 index 0000000..0ceb5bb --- /dev/null +++ b/src/bf_pktpy/library/specs/templates/ipv6_ext_hdr_routing.py @@ -0,0 +1,125 @@ +#!/usr/bin/env python + + +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" IPv6ExtHdrRouting template """ +from bf_pktpy.library.specs.base import Base + + +# ============================================================================= + + +class IPv6ExtHdrRouting(Base): + """IPv6ExtHdrRouting class""" + + name = "IPv6ExtHdrRouting" + + _len = 0 # computed value from len of addresses + + def __init__(self, **kwargs): + Base._prepare_kwargs(kwargs) + + super(IPv6ExtHdrRouting, self).__init__() + self.nh = kwargs.pop("nh", 59) + length = kwargs.pop("len", None) + if length is not None: + self.len = length + self.type = kwargs.pop("type", 0) + self.segleft = kwargs.pop("segleft", 4) + self.reserved = kwargs.pop("reserved", 0) + self.addresses = kwargs.pop("addresses", []) + + if kwargs: + raise ValueError("Unsupported key(s) %s" % list(kwargs.keys())) + + def _combine(self, body_copy): + if body_copy.name == "IP": + self._body = body_copy + self.nh = 4 + return self + if body_copy.name == "IPv6": + self._body = body_copy + self.nh = 41 + return self + if body_copy.name == "UDP": + self._body = body_copy + self.nh = 17 + return self + if body_copy.name == "TCP": + self._body = body_copy + self.nh = 6 + return self + if body_copy.name == "GRE": + self._body = body_copy + self.nh = 47 + return self + if body_copy.name == "ICMPv6Unknown": + self._body = body_copy + self.nh = 58 + return self + if body_copy.name == "IPv6ExtHdrRouting": + self._body = body_copy + self.nh = 43 + return self + if body_copy.name == "Ether": + self._body = body_copy + return self + + raise ValueError("Unsupported binding") + + @property + def len(self): + return self._len + + @len.setter + def len(self, custom_len): + self.lock("len") + self._len = custom_len + + @property + def payload_len(self): + """Get payload length""" + plen = 0 + if hasattr(self._body, "total_len"): + plen = self._body.total_len + return plen + + @property + def hdr_len(self): + """Get header size""" + return 8 + len(self.addresses) * 16 + + @property + def total_len(self): + """Get full length""" + return self.hdr_len + self.payload_len + + def _members(self): + """Member information""" + length = ( + self.len if self.is_lock("len") is not None else 2 * len(self.addresses) + ) + members = ( + ("nh", self.nh, 8), + ("len", length, 8), + ("type", self.type, 8), + ("segleft", self.segleft, 8), + ("reserved", self.reserved, 32), + ("addresses", self.addresses, len(self.addresses) * 16 * 8), + ) + return {"ipv6_ext_hdr_routing": members} + + +# ============================================================================= diff --git a/src/bf_pktpy/library/specs/templates/mpls.py b/src/bf_pktpy/library/specs/templates/mpls.py new file mode 100644 index 0000000..4906d0d --- /dev/null +++ b/src/bf_pktpy/library/specs/templates/mpls.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python + + +# Copyright (c) 2022 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" MPLS template """ +from bf_pktpy.library.specs.packet import Packet +from bf_pktpy.library.fields import BitField, ByteField + + +class MPLS(Packet): + name = "MPLS" + fields_desc = [ + # lambda here to ensure, that label will change in bindings only if user didn't + # provide his value. + BitField("label", lambda _: 3, 20), + BitField("cos", 0, 3), + BitField("s", 1, 1), + ByteField("ttl", 0), + ] + + def _combine(self, body_copy): + if body_copy.name == "MPLS": + self._body = body_copy + self.s = 0 + return self + if body_copy.name == "Ether": + self._body = body_copy + return self + if body_copy.name == "IP": + self._body = body_copy + if self.label is None: + self.label = 0 + return self + if body_copy.name == "IPv6": + self._body = body_copy + if self.label is None: + self.label = 2 + return self + + raise ValueError("Unsupported binding") diff --git a/src/bf_pktpy/library/specs/templates/payload.py b/src/bf_pktpy/library/specs/templates/payload.py new file mode 100644 index 0000000..d2eb270 --- /dev/null +++ b/src/bf_pktpy/library/specs/templates/payload.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python + + +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" Payload template """ +from bf_pktpy.library.specs.base import Base + + +# ============================================================================= +class Payload(Base): + """Payload class + + Definition: + Payload + pattern (str) + data (str) + + Examples: + | + create + | payload = Payload(pattern=.., data=..) + | + make change + | payload.pattern = + | payload.data = + """ + + def __init__(self, pattern="ByteIncrement", data=""): + self.pattern = pattern + self.data = data + + def _members(self): + """Member information""" + members = (("pattern", self.pattern), ("data", self.data)) + return {"payload": members} + + +# ============================================================================= diff --git a/src/bf_pktpy/library/specs/templates/raw.py b/src/bf_pktpy/library/specs/templates/raw.py new file mode 100644 index 0000000..8701e5b --- /dev/null +++ b/src/bf_pktpy/library/specs/templates/raw.py @@ -0,0 +1,13 @@ +# Copyright (c) 2022 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +from bf_pktpy.library.specs.packet import Packet +from bf_pktpy.library.fields import StrField + + +class Raw(Packet): + + name = "Raw" + fields_desc = [StrField("load", b"")] + + def str(self): + return self.load diff --git a/src/bf_pktpy/library/specs/templates/sfc/__init__.py b/src/bf_pktpy/library/specs/templates/sfc/__init__.py new file mode 100644 index 0000000..166a7a6 --- /dev/null +++ b/src/bf_pktpy/library/specs/templates/sfc/__init__.py @@ -0,0 +1,26 @@ +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from bf_pktpy.library.specs.templates.sfc.mac_control_class_based_flow_control import ( + MACControlClassBasedFlowControl, +) +from bf_pktpy.library.specs.templates.sfc.sfc_pause import SfcPause +from bf_pktpy.library.specs.templates.sfc.sfc_fabric_header import SfcFabricHeader +from bf_pktpy.library.specs.templates.sfc.sfc_cpu_header import SfcCPUHeader +from bf_pktpy.library.specs.templates.sfc.sfc_roce import ( + RoceOpcode, + IB_BTH, + IB_RETH, + IB_AETH, + IB_IMM, + IB_ICRC, +) diff --git a/src/bf_pktpy/library/specs/templates/sfc/mac_control_class_based_flow_control.py b/src/bf_pktpy/library/specs/templates/sfc/mac_control_class_based_flow_control.py new file mode 100644 index 0000000..8d03d02 --- /dev/null +++ b/src/bf_pktpy/library/specs/templates/sfc/mac_control_class_based_flow_control.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python + + +# Copyright (c) 2022 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### + +from bf_pktpy.library.specs.packet import Packet +from bf_pktpy.library.fields import BitField, ByteField, ShortField + + +class MACControlClassBasedFlowControl(Packet): + name = "MACControlClassBasedFlowControl" + fields_desc = [ + ShortField("_op_code", 0x0101), + 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), + ] diff --git a/src/bf_pktpy/library/specs/templates/sfc/sfc_cpu_header.py b/src/bf_pktpy/library/specs/templates/sfc/sfc_cpu_header.py new file mode 100644 index 0000000..a1b22cc --- /dev/null +++ b/src/bf_pktpy/library/specs/templates/sfc/sfc_cpu_header.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python + + +# Copyright (c) 2022 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### + +from bf_pktpy.library.specs.packet import Packet +from bf_pktpy.library.fields import BitField, ShortField + + +class SfcCPUHeader(Packet): + name = "CPUHeader" + fields_desc = [ + BitField("tx_bypass", 0, 1), + BitField("capture_ts", 0, 1), + BitField("reserved", 0, 1), + BitField("egress_queue", 0, 5), + ShortField("ingress_port", 0), + ShortField("port_lag_index", 0), + ShortField("ingress_bd", 0), + ShortField("reason_code", 0), + ShortField("ether_type", 0), + ] diff --git a/src/bf_pktpy/library/specs/templates/sfc/sfc_fabric_header.py b/src/bf_pktpy/library/specs/templates/sfc/sfc_fabric_header.py new file mode 100644 index 0000000..1dcc0a9 --- /dev/null +++ b/src/bf_pktpy/library/specs/templates/sfc/sfc_fabric_header.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python + + +# Copyright (c) 2022 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### + +from bf_pktpy.library.specs.packet import Packet +from bf_pktpy.library.fields import BitField, ByteField + + +class SfcFabricHeader(Packet): + name = "FabricHeader" + fields_desc = [ + ByteField("reserved", 0), + BitField("color", 0, 3), + BitField("qos", 0, 5), + ByteField("reserved2", 0), + ] diff --git a/src/bf_pktpy/library/specs/templates/sfc/sfc_pause.py b/src/bf_pktpy/library/specs/templates/sfc/sfc_pause.py new file mode 100644 index 0000000..a99c26c --- /dev/null +++ b/src/bf_pktpy/library/specs/templates/sfc/sfc_pause.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python + + +# Copyright (c) 2022 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### + +from bf_pktpy.library.specs.packet import Packet +from bf_pktpy.library.fields import BitField, ByteField, ShortField + + +class SfcPause(Packet): + name = "SfcPause" + fields_desc = [ + ByteField("version", 0), + ByteField("dscp", 0), + ShortField("duration_us", 0), + BitField("pad_0", 0, 112), + ] diff --git a/src/bf_pktpy/library/specs/templates/sfc/sfc_roce.py b/src/bf_pktpy/library/specs/templates/sfc/sfc_roce.py new file mode 100644 index 0000000..965281c --- /dev/null +++ b/src/bf_pktpy/library/specs/templates/sfc/sfc_roce.py @@ -0,0 +1,150 @@ +#!/usr/bin/env python + + +# Copyright (c) 2022 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" RoCE headers """ + +from enum import Enum + +from bf_pktpy.library.specs.packet import Packet +from bf_pktpy.library.fields import ( + ByteEnumField, + XBitField, + XShortField, + X3ByteField, + XIntField, + XLongField, +) + + +class RoceOpcode(Enum): + UC_SEND_FIRST = 0b00100000 + UC_SEND_MIDDLE = 0b00100001 + UC_SEND_LAST = 0b00100010 + UC_SEND_LAST_IMMEDIATE = 0b00100011 + UC_SEND_ONLY = 0b00100100 + UC_SEND_ONLY_IMMEDIATE = 0b00100101 + UC_RDMA_WRITE_FIRST = 0b00100110 + UC_RDMA_WRITE_MIDDLE = 0b00100111 + UC_RDMA_WRITE_LAST = 0b00101000 + UC_RDMA_WRITE_LAST_IMMEDIATE = 0b00101001 + UC_RDMA_WRITE_ONLY = 0b00101010 + UC_RDMA_WRITE_ONLY_IMMEDIATE = 0b00101011 + RC_RDMA_WRITE_ONLY = 0b00001010 + RC_RDMA_WRITE_ONLY_IMMEDIATE = 0b00001011 + RC_WRITE_ACK = 0b0001000 + + @staticmethod + def to_dict(): + return {i.name: i.value for i in RoceOpcode} + + +class IB_BTH(Packet): + name = "IB_BTH" + fields_desc = [ + ByteEnumField("opcode", 0b0, RoceOpcode.to_dict()), + XBitField("se", 0, 1), + XBitField("migration_req", 1, 1), # ??? + XBitField("pad_count", 0, 2), + XBitField("transport_version", 0, 4), + XShortField("partition_key", 0xFFFF), + XBitField("f_res1", 0, 1), + XBitField("b_res1", 0, 1), + XBitField("reserved", 0, 6), + X3ByteField("dst_qp", 0), + XBitField("ack_req", 0, 1), + XBitField("reserved2", 0, 7), + X3ByteField("psn", 0), + ] + + def _combine(self, body_copy): + if body_copy.name in ("IB_RETH", "IB_IMM", "IB_AETH"): + self._body = body_copy + return self + + raise ValueError("Unsupported binding") + + +class IB_RETH(Packet): + name = "IB_RETH" + + fields_desc = [ + XLongField("addr", 0), + XIntField("rkey", 0), + XIntField("len", 0), + ] + + def _combine(self, body_copy): + if body_copy.name == "IB_IMM": + self._body = body_copy + return self + + raise ValueError("Unsupported binding") + + +class IB_AETH(Packet): + name = "IB_AETH" + fields_desc = [ + XBitField("res", 0, 1), + XBitField("opcode", 0, 2), # ??? + XBitField("credit_count", 0, 5), + X3ByteField("message_seq", 0), + ] + + def _combine(self, body_copy): + if body_copy.name == "IB_ICRC": + self._body = body_copy + return self + + raise ValueError("Unsupported binding") + + +class IB_IMM(Packet): + name = "IB_IMM" + fields_desc = [XIntField("imm", 0)] + + +class IB_ICRC(Packet): + name = "IB_ICRC" + fields_desc = [XIntField("icrc", None)] + + +####################################################################################### +# classes not yet implemented + their desired bindings + +# class IB_GRH(Packet): +# name = "IB_GRH" +# fields_desc = [ +# XBitField("ipver", 6, 4), +# XBitField("tclass", 2, 8), +# XBitField("flowlabel", 0, 20), +# XShortField("paylen", 0), +# XByteField("nxthdr", 27), +# XByteField("hoplmt", 64), +# IP6Field("sgid", "::1"), +# IP6Field("dgid", "::1") +# ] + +# class IB_Payload(Packet): +# name = "IB_Payload" +# fields_desc = [ +# FieldListField('data', None, SignedIntField('', 0), +# length_from=lambda pkt: len(pkt.payload) - 4) +# ] + +# bind_layers(IB_BTH, IB_Payload) +# bind_layers(IB_RETH, IB_Payload) +# bind_layers(IB_IMM, IB_Payload) +# bind_layers(IB_Payload, IB_ICRC) diff --git a/src/bf_pktpy/library/specs/templates/tcp.py b/src/bf_pktpy/library/specs/templates/tcp.py new file mode 100644 index 0000000..f7c5d2e --- /dev/null +++ b/src/bf_pktpy/library/specs/templates/tcp.py @@ -0,0 +1,205 @@ +#!/usr/bin/env python + + +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" TCP template """ +import math + +from bf_pktpy.library.specs.base import Base +from bf_pktpy.library.specs.templates.tcpoption import TCPOptionPlaceholder + +# ============================================================================= +from bf_pktpy.library.specs.validate_sport_dport import ValidateSportDport + + +class TCP(Base, ValidateSportDport): + """TCP class + + Definition: + TCP + sport (int) + dport (int) + seq (int) + ack (int) + dataofs (int) + reserved (int) + flags (int) + window (int) + chksum (int) + urgptr (int) + options (str) + + Examples: + | + create + | tcp = TCP(sport=.., dport=.., ) + | + make change + | tcp.sport = + | tcp.dport = + | ... + | + """ + + name = "TCP" + + _sport = 0 + _dport = 0 + _chksum = 0 + + def __init__(self, **kwargs): + Base._prepare_kwargs(kwargs) + self.flags_field(kwargs) + + super(TCP, self).__init__() + self.sport = kwargs.pop("sport", 20) + self.dport = kwargs.pop("dport", 80) + self.seq = kwargs.pop("seq", 0) + self.ack = kwargs.pop("ack", 0) + self.dataofs = kwargs.pop("dataofs", 0) + self.reserved = kwargs.pop("reserved", 0) + self.flags = kwargs.pop("flags", 2) + self.window = kwargs.pop("window", 8192) + self._chksum = 0 + if "chksum" in kwargs: + self.chksum = kwargs.pop("chksum") + self.urgptr = kwargs.pop("urgptr", 0) + self.options = self._fix_options(kwargs.pop("options", "")) + if kwargs: + raise ValueError("Unsupported key(s) %s" % list(kwargs.keys())) + + def flags_field(self, kwargs): + potential_f = kwargs.get("flags", None) + if potential_f: + flags = "FSRPAUECN" + num = 0 + if isinstance(potential_f, int): + kwargs["flags"] = potential_f + return + for f in potential_f: + num += int(math.pow(2, flags.find(f))) if flags.index(f) >= 0 else 0 + kwargs["flags"] = num + + @property + def hdr_len(self): + """Get header length""" + if not self.options: + return 20 + return 20 + len(self.options.b) + + @property + def total_len(self): + """Get full length""" + if self._body: + return self.hdr_len + len(self._body) + return self.hdr_len + + @property + def chksum(self): + return self._chksum + + @chksum.setter + def chksum(self, custom_chksum): + self.lock("chksum") + self._chksum = custom_chksum + + def reset_chksum(self): + self.lock("chksum", False) + self._chksum = 0 + + @staticmethod + def from_hex(hex_str): + """Create object from hex value""" + sport = int(hex_str[0:4], 16) + dport = int(hex_str[4:8], 16) + seq = int(hex_str[8:16], 16) + ack = int(hex_str[16:24], 16) + dataofs = int(hex_str[24:25], 16) + reserved = int(hex_str[25:26], 16) >> 1 + flags = int(hex_str[25:26], 16) % 2 * 512 + int(hex_str[26:28], 16) + window = int(hex_str[28:32], 16) + chksum = int(hex_str[32:36], 16) + urgptr = int(hex_str[36:40], 16) + opt_ = hex_str[40:] + options = "" + if opt_: + options = "" + for idx in range(len(opt_)): + temp = bin(int(opt_[idx], 16)) + options += temp[2:] + if options: + pad = (32 - len(options) % 32) * "0" + options += pad + kwargs = { + "sport": sport, + "dport": dport, + "seq": seq, + "ack": ack, + "dataofs": dataofs, + "reserved": reserved, + "flags": flags, + "window": window, + "chksum": chksum, + "urgptr": urgptr, + "options": options, + } + return TCP(**kwargs) + + def _members(self): + """Member information""" + dataofs = self.hdr_len // 4 + + if self.options: + opt = self.options.b + members = ( + ("sport", self.sport, 16), + ("dport", self.dport, 16), + ("seq", self.seq, 32), + ("ack", self.ack, 32), + ("dataofs", dataofs, 4), + ("reserved", self.reserved, 3), + ("flags", self.flags, 9), + ("window", self.window, 16), + ("chksum", self.chksum, 16), + ("urgptr", self.urgptr, 16), + ("options", int(opt.encode("hex"), 16), len(opt) * 8), + ) + else: + members = ( + ("sport", self.sport, 16), + ("dport", self.dport, 16), + ("seq", self.seq, 32), + ("ack", self.ack, 32), + ("dataofs", dataofs, 4), + ("reserved", self.reserved, 3), + ("flags", self.flags, 9), + ("window", self.window, 16), + ("chksum", self.chksum, 16), + ("urgptr", self.urgptr, 16), + ) + return {"tcp": members} + + def _fix_options(self, options): + if not options: + return "" + if isinstance(options, TCPOptionPlaceholder): + return options + if isinstance(options, list): + op_bytes = b"" + for opt in options: + op_bytes += opt.b if isinstance(opt, TCPOptionPlaceholder) else opt + return TCPOptionPlaceholder(op_bytes) + return TCPOptionPlaceholder(options) + + +# ============================================================================= diff --git a/src/bf_pktpy/library/specs/templates/tcpoption.py b/src/bf_pktpy/library/specs/templates/tcpoption.py new file mode 100644 index 0000000..0100424 --- /dev/null +++ b/src/bf_pktpy/library/specs/templates/tcpoption.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python + + +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" TCPOption template """ + +from bf_pktpy.library.specs.base import Base + +# ============================================================================= + + +class TCPOptionPlaceholder(Base): + """TCPOption class + Definition: + TCPOption + + Examples: + | + create + | TCPOption = TCPOption('0x14040000') + | + """ + + name = "TCPOptionPlaceholder" + + def __init__(self, *args): + super(TCPOptionPlaceholder, self).__init__() + + self.b = None + if args: + self.b = args[0] + + def __int__(self): + return int(self.hex().replace(" ", ""), 16) + + def _members(self): + """Member information""" + return {"tcpoption": {("bin", self.b, len(self.b) * 2)}} + + +# ============================================================================= diff --git a/src/bf_pktpy/library/specs/templates/udp.py b/src/bf_pktpy/library/specs/templates/udp.py new file mode 100644 index 0000000..4e109a4 --- /dev/null +++ b/src/bf_pktpy/library/specs/templates/udp.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python + + +# Copyright (c) 2022 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" UDP template """ +from bf_pktpy.library.helpers.bin import to_bin +from bf_pktpy.library.helpers.chksum import checksum +from bf_pktpy.library.specs.packet import Packet +from bf_pktpy.library.fields import ShortField, XShortField + + +def total_len(packet): + return packet.total_len + + +def udp_checksum(packet): + """Calculate UDP checksum""" + + def members_lookup(members, field_name): + return next((val, size) for name, val, size in members if name == field_name) + + binary = "" + ip_layer = packet.underlayer + if ip_layer is not None and ip_layer.name in ("IP", "IPv6"): + # noinspection PyProtectedMember + ip_members = list(ip_layer._members().values())[0] + src_ip = to_bin(*members_lookup(ip_members, "src")) + dst_ip = to_bin(*members_lookup(ip_members, "dst")) + proto_field = "proto" if ip_layer.name == "IP" else "nh" + l4_proto = to_bin(*members_lookup(ip_members, proto_field)) + ttlen = ip_layer.total_len + temp = ttlen - ip_layer.hdr_len + l4_len = to_bin(temp, 16) + resved = "00000000" + binary += ( + src_ip + dst_ip + l4_len + resved + l4_proto + packet["UDP"].bin(chksum=0) + ) + if len(binary) % 16 > 0: + binary += "00000000" + + calculated_checksum = checksum(binary) + # According to RFC768 if the result checksum is 0, it should be set to 0xFFFF # noqa: E501 + return calculated_checksum if calculated_checksum != 0 else 0xFFFF + + +class UDP(Packet): + """UDP class + + Definition: + UDP + sport (int) + dport (int) + len (int) + chksum (int) + + Examples: + | + create + | udp = UDP(sport=.., dport=.., ) + | + make change + | udp.sport = + | udp.dport = + | ... + """ + + name = "UDP" + fields_desc = [ + ShortField("sport", 53), + ShortField("dport", 53), + ShortField("len", total_len), + XShortField("chksum", udp_checksum), + ] + + def _combine(self, body_copy): + if body_copy.name == "BFD": + self._body = body_copy + self.sport = 3784 + self.dport = 3784 + return self + if body_copy.name == "BOOTP": + self._body = body_copy + self.sport = 68 + self.dport = 67 + return self + if body_copy.name == "VXLAN": + self._body = body_copy + self.sport = 4789 + self.dport = 4789 + return self + if body_copy.name == "MPLS": + self._body = body_copy + self.dport = 6635 + return self + if body_copy.name in ("DtelReportHdr", "DtelReportV2Hdr"): + self._body = body_copy + self.dport = 32766 + return self + if body_copy.name == "GTPU": + self._body = body_copy + return self + if body_copy.name == "IB_BTH": + self._body = body_copy + self.dport = 4791 + return self + if body_copy.name == "SfcPause": + self._body = body_copy + return self + + raise ValueError("Unsupported binding") + + @staticmethod + def from_hex(hex_str): + """Create object from hex value""" + sport = int(hex_str[0:4], 16) + dport = int(hex_str[4:8], 16) + len_ = int(hex_str[8:12], 16) + chksum = int(hex_str[12:16], 16) + kwargs = {"sport": sport, "dport": dport, "len": len_, "chksum": chksum} + return UDP(**kwargs) diff --git a/src/bf_pktpy/library/specs/templates/vxlan.py b/src/bf_pktpy/library/specs/templates/vxlan.py new file mode 100644 index 0000000..337ce73 --- /dev/null +++ b/src/bf_pktpy/library/specs/templates/vxlan.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python + + +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" VXLAN template """ +from bf_pktpy.library.specs.base import Base + + +# ============================================================================= +class VXLAN(Base): + """ + Definition: + VXLAN: + vni (int) + reserved1 (int) + reserved2 (int) + Example: + | + create + | VXLAN(vni=..., reserved1=..., ...) + | + make change + | vxlan.vni = + | vxlan.reserved1 = + """ + + name = "VXLAN" + + def __init__(self, **kwargs): + Base._prepare_kwargs(kwargs) + + super(VXLAN, self).__init__() + + self.flags = kwargs.pop("flags", 0x08) + self.reserved0 = kwargs.pop("reserved0", 0) + self.NextProtocol = kwargs.pop("NextProtocol", 0) + self.reserved1 = kwargs.pop("reserved1", 0) + self.vni = kwargs.pop("vni", 0) + self.reserved2 = kwargs.pop("reserved2", 0) + if kwargs: + raise ValueError("Unsupported key(s) %s" % list(kwargs.keys())) + + def _combine(self, body_copy): + if body_copy.name in ("Ether", "IP", "IPv6"): + self._body = body_copy + return self + + raise ValueError("Unsupported binding") + + def _members(self): + """Members information""" + members = [("flags", self.flags, 8)] + if self.flags & 0x04: + members.append(("reserved0", self.reserved0, 16)) + members.append(("NextProtocol", self.NextProtocol, 8)) + else: + members.append(("reserved1", self.reserved1, 24)) + + members.append(("vni", self.vni, 24)) + members.append(("reserved2", self.reserved2, 8)) + + return {"vxlan": members} + + +# ============================================================================= diff --git a/src/bf_pktpy/library/specs/templates/xnt/__init__.py b/src/bf_pktpy/library/specs/templates/xnt/__init__.py new file mode 100644 index 0000000..e31c73e --- /dev/null +++ b/src/bf_pktpy/library/specs/templates/xnt/__init__.py @@ -0,0 +1,15 @@ +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from bf_pktpy.library.specs.templates.xnt.int_meta import XntIntMeta +from bf_pktpy.library.specs.templates.xnt.int_l45_head import XntIntL45Head +from bf_pktpy.library.specs.templates.xnt.int_l45_tail import XntIntL45Tail diff --git a/src/bf_pktpy/library/specs/templates/xnt/int_l45_head.py b/src/bf_pktpy/library/specs/templates/xnt/int_l45_head.py new file mode 100644 index 0000000..40c3eb0 --- /dev/null +++ b/src/bf_pktpy/library/specs/templates/xnt/int_l45_head.py @@ -0,0 +1,21 @@ +# Copyright (c) 2022 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +from bf_pktpy.library.specs.packet import Packet +from bf_pktpy.library.fields import XByteField + + +class XntIntL45Head(Packet): + name = "INT_L45_HEAD" + fields_desc = [ + XByteField("int_type", 0x01), + XByteField("rsvd0", 0x00), + XByteField("length", 0x00), + XByteField("rsvd1", 0x00), + ] + + def _combine(self, body_copy): + if body_copy.name == "INT_META": + self._body = body_copy + return self + + raise ValueError("Unsupported binding") diff --git a/src/bf_pktpy/library/specs/templates/xnt/int_l45_tail.py b/src/bf_pktpy/library/specs/templates/xnt/int_l45_tail.py new file mode 100644 index 0000000..0a87278 --- /dev/null +++ b/src/bf_pktpy/library/specs/templates/xnt/int_l45_tail.py @@ -0,0 +1,16 @@ +# Copyright (c) 2022 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +from bf_pktpy.library.specs.packet import Packet +from bf_pktpy.library.fields import ( + XByteField, + XShortField, +) + + +class XntIntL45Tail(Packet): + name = "INT_L45_TAIL" + fields_desc = [ + XByteField("next_proto", 0x01), + XShortField("proto_param", 0x0000), + XByteField("rsvd", 0x00), + ] diff --git a/src/bf_pktpy/library/specs/templates/xnt/int_meta.py b/src/bf_pktpy/library/specs/templates/xnt/int_meta.py new file mode 100644 index 0000000..ef87910 --- /dev/null +++ b/src/bf_pktpy/library/specs/templates/xnt/int_meta.py @@ -0,0 +1,23 @@ +# Copyright (c) 2022 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +from bf_pktpy.library.specs.packet import Packet +from bf_pktpy.library.fields import ( + BitField, + ShortField, +) + + +class XntIntMeta(Packet): + name = "INT_META" + fields_desc = [ + BitField("ver", 0, 4), + BitField("rep", 0, 2), + BitField("c", 0, 1), + BitField("e", 0, 1), + BitField("rsvd1", 0, 3), + BitField("ins_cnt", 0, 5), + BitField("max_hop_cnt", 32, 8), + BitField("total_hop_cnt", 0, 8), + ShortField("inst_mask", 0), + ShortField("rsvd2", 0x0000), + ] diff --git a/src/bf_pktpy/library/specs/udp.py b/src/bf_pktpy/library/specs/udp.py new file mode 100644 index 0000000..457b7f9 --- /dev/null +++ b/src/bf_pktpy/library/specs/udp.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python + + +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" UDP class """ +from bf_pktpy.library.specs.container import Container +from bf_pktpy.library.specs.templates.udp import UDP as UDPTemplate + + +# ============================================================================= +class UDP(Container): + """UDP class""" + + fields = "sport dport len chksum".split() + + def __init__(self, **kwargs): + super(UDP, self).__init__(UDPTemplate, **kwargs) + + def __truediv__(self, payload): + self.clear() # not done + return self + + +# ============================================================================= diff --git a/src/bf_pktpy/library/specs/validate.py b/src/bf_pktpy/library/specs/validate.py new file mode 100644 index 0000000..beb0a8e --- /dev/null +++ b/src/bf_pktpy/library/specs/validate.py @@ -0,0 +1,334 @@ +#!/usr/bin/env python + + +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" Enforce module to validate instance variables """ +import ipaddress +import re +import six + +from bf_pktpy.library.specs.constant import MAC_PATTERN + + +# ============================================================================= +def is_hex(value): + """Check if value is hex""" + try: + int(str(value), 16) + return True + except ValueError: + return False + + +def remove_unicode(value): + """ + Remove unicode and return a unicode-free string + :param value: string or unicode string + :return: unicode-free string + """ + + # TODO temporary fix for compatibility + if isinstance(value, six.string_types): + return six.ensure_str(value) + return value + + +class ToBeOneOfThese: + """Descriptor to validate supported value""" + + def __init__(self, itemlist, default=None): + self.default = default + self.itemlist = [each for each in itemlist] + self.data = {} + + def __get__(self, instance, owner): + return self.data.get(instance, self.default) + + def __set__(self, instance, value): + if isinstance(value, str) and not value: + self.data[instance] = "" + return + if value not in self.itemlist: + print("-ERROR- Value %r is not supported" % value) + return + self.data[instance] = value + + +class ToBeIntegerInRange: + """Descriptor to validate in-range value""" + + def __init__(self, min_val, max_val, default=0): + self.default = default + self.min_val = min_val + self.max_val = max_val + self.data = {} + + def __get__(self, instance, owner): + return self.data.get(instance, self.default) + + def __set__(self, instance, value): + if isinstance(value, int): + if self.min_val <= value <= self.max_val: + self.data[instance] = value + return + print("-ERROR- Value must be between %d and %d" % (self.min_val, self.max_val)) + + +class ToBeBitField(ToBeIntegerInRange): + def __init__(self, bit_count, default=0): + ToBeIntegerInRange.__init__(self, 0, 2**bit_count - 1, default) + + +class ToBeByteField(ToBeBitField): + def __init__(self, default=0): + ToBeBitField.__init__(self, 8, default) + + +class ToBeShortField(ToBeBitField): + def __init__(self, default=0): + ToBeBitField.__init__(self, 16, default) + + +class ToBeIntegerField(ToBeBitField): + def __init__(self, default=0): + ToBeBitField.__init__(self, 32, default) + + +class ToBeLongField(ToBeBitField): + def __init__(self, default=0): + ToBeBitField.__init__(self, 64, default) + + +class ToBeStringInRange: + """Descriptor to validate in-range value""" + + def __init__(self, min_val, max_val, default=0): + self.default = default + self.min_val = min_val + self.max_val = max_val + self.data = {} + + def __get__(self, instance, owner): + return self.data.get(instance, self.default) + + def __set__(self, instance, value): + if isinstance(value, str) and not value: + self.data[instance] = "" + return + if isinstance(value, (int, str)): + try: + int(str(value), 16) + if self.min_val <= eval(str(value)) <= self.max_val: + self.data[instance] = str(eval(str(value))) + return + except ValueError: + pass + print("-ERROR- Value must be between %d and %d" % (self.min_val, self.max_val)) + + +class ToBeListOfIntegerInRange: + """Descriptor to validate in-range value""" + + def __init__(self, min_val, max_val, default=None): + self.default = default or [64] + self.min_val = min_val + self.max_val = max_val + self.data = {} + + def __get__(self, instance, owner): + return self.data.get(instance, self.default) + + def __set__(self, instance, values): + if isinstance(values, (tuple, list)): + for value in values: + if not isinstance(value, int): + print("-ERROR- Value must be integer") + return + if not self.min_val <= value <= self.max_val: + print( + "-ERROR- Value must be between %d and %d" + % (self.min_val, self.max_val) + ) + return + self.data[instance] = values + return + print("-ERROR- Value must be of type 'list'") + + +class ToBeNonEmptyString: + """Descriptor to validate non-empty value""" + + def __init__(self, default=""): + self.default = default + self.data = {} + + def __get__(self, instance, owner): + return self.data.get(instance, self.default) + + def __set__(self, instance, value): + if isinstance(value, str): + if value: + self.data[instance] = value + return + print("-ERROR- Value cannot be an empty string") + + +class ToBeBinaryString: + """Descriptor to validate binary string value""" + + def __init__(self, default=""): + self.default = default + self.data = {} + + def __get__(self, instance, owner): + return self.data.get(instance, self.default) + + def __set__(self, instance, value): + if isinstance(value, str): + if value: + temp = value.replace("0", "").replace("1", "") + if len(temp): + print("-ERROR- Value must be in binary format") + return + self.data[instance] = value + return + else: + self.data[instance] = "" + return + print("-ERROR- Value cannot be an empty string") + + +class ToBeValidMacAddress: + """Descriptor to validate a valid mac address""" + + def __init__(self, default="00:00:00:00:00:00"): + self.default = default + self.check_mac = re.compile(MAC_PATTERN, re.I) + self.data = {} + + def __get__(self, instance, owner): + return self.data.get(instance, self.default) + + def __set__(self, instance, value): + # TODO temporary fix for compatibility + if type(value).__name__ in ("int", "long"): + hex_bytes = [ + "{}{}".format(a, b) + for a, b in zip(*[iter("{:012x}".format(value))] * 2) + ] + self.data[instance] = ":".join(hex_bytes) + return + + if isinstance(value, str) and not value: + self.data[instance] = self.default + return + if "-" in value: + value = value.replace("-", ":") + elif is_hex(value) and len(value) == 14: + value = value[2:] + value = ":".join(["%s" % (value[i : i + 2]) for i in range(0, 12, 2)]) + elif re.findall(r"[\._-]", value): + value = re.sub(r"[\._-]", "", value) + value = ":".join(["%s" % (value[i : i + 2]) for i in range(0, 12, 2)]) + if not self.check_mac.match(value): + print("-ERROR- Mac address is not valid") + return + self.data[instance] = value.upper() + + +class ToBeValidIPv4Address: + """Descriptor to validate a valid ip address""" + + def __init__(self, default="0.0.0.0"): + self.default = default + self.data = {} + + def __get__(self, instance, owner): + return self.data.get(instance, self.default) + + def __set__(self, instance, value): + if isinstance(value, str) and not value: + self.data[instance] = self.default + return + if isinstance(value, (int, str)): + try: + value = str(ipaddress.IPv4Address(value)) + self.data[instance] = value + return + except ValueError: + if is_hex(value) and (0 <= eval(str(value)) < pow(2, 32)): + self.data[instance] = str(eval(str(value))) + return + print("-ERROR- Value must be a valid IPv4 Address") + + +class ToBeValidIPv6Address: + """Descriptor to validate a valid ipv6 address""" + + def __init__(self, default=""): + self.default = default + self.data = {} + + def __get__(self, instance, owner): + return self.data.get(instance, self.default) + + def __set__(self, instance, value): + if isinstance(value, str) and not value: + self.data[instance] = "" + return + if isinstance(value, (int, str)): + try: + ipaddress.IPv6Address(value) + self.data[instance] = value + return + except ValueError: + if is_hex(value) and (0 <= eval(str(value)) < pow(2, 128)): + self.data[instance] = hex(eval(str(value))) + return + print("-ERROR- Value must be a valid IPv6 Address") + + +class ToBeValidListOfIPv6Addresses: + """Descriptor to validate a list of valid ipv6 addresses""" + + def __init__(self, default=None): + self.default = default or [] + self.data = {} + + def __get__(self, instance, owner): + return self.data.get(instance, self.default) + + def __set__(self, instance, values): + if isinstance(values, (tuple, list)): + for i in range(len(values)): + value = values[i] + if isinstance(value, str) and not value: + print("-ERROR- Each value in list must be str or int") + return + if isinstance(value, (int, str)): + try: + ipaddress.IPv6Address(value) + except ValueError: + if is_hex(value) and (0 <= eval(str(value)) < pow(2, 128)): + values[i] = hex(eval(str(value))) + continue + print("-ERROR- Value from list must be a valid IPv6 " "address") + return + self.data[instance] = values + return + print("-ERROR- Value must be of type 'list'") + + +# ============================================================================= diff --git a/src/bf_pktpy/library/specs/validate_sport_dport.py b/src/bf_pktpy/library/specs/validate_sport_dport.py new file mode 100644 index 0000000..1dbe014 --- /dev/null +++ b/src/bf_pktpy/library/specs/validate_sport_dport.py @@ -0,0 +1,46 @@ +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +class ValidateSportDport: + @property + def sport(self): + return self._sport + + @sport.setter + def sport(self, value): + self._sport = value + + @property + def dport(self): + return self._dport + + @dport.setter + def dport(self, value): + self._dport = value + + # these 4 properties are only for cross compatibility + @property + def src(self): + return self.sport + + @src.setter + def src(self, value): + self.sport = value + + @property + def dst(self): + return self.dport + + @dst.setter + def dst(self, value): + self.dport = value diff --git a/src/bf_pktpy/library/specs/validate_src_dst.py b/src/bf_pktpy/library/specs/validate_src_dst.py new file mode 100644 index 0000000..501bf8c --- /dev/null +++ b/src/bf_pktpy/library/specs/validate_src_dst.py @@ -0,0 +1,40 @@ +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import ipaddress + +from bf_pktpy.library.specs.validate import remove_unicode + + +class ValidateSrcDst: + @property + def src(self): + return self._src + + @src.setter + def src(self, value): + if isinstance(value, int): + value = str(ipaddress.ip_address(value)) + else: + value = remove_unicode(value) + self._src = value + + @property + def dst(self): + return self._dst + + @dst.setter + def dst(self, value): + if isinstance(value, int): + value = str(ipaddress.ip_address(value)) + else: + value = remove_unicode(value) + self._dst = value diff --git a/src/bf_pktpy/library/utils/__init__.py b/src/bf_pktpy/library/utils/__init__.py new file mode 100644 index 0000000..177a4a8 --- /dev/null +++ b/src/bf_pktpy/library/utils/__init__.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python + + +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +from bf_pktpy.library.utils.interface import Interface +from bf_pktpy.library.utils.stream import Stream +from bf_pktpy.library.utils.listener import Listener +from bf_pktpy.library.utils.decoder import Decoder +from bf_pktpy.library.utils.answer import Answer, Unanswer +from bf_pktpy.library.utils.sniff import Sniffer, Received +from bf_pktpy.library.utils.bridge_and_sniff import BridgeSniff +from bf_pktpy.library.utils.ls import ls diff --git a/src/bf_pktpy/library/utils/answer.py b/src/bf_pktpy/library/utils/answer.py new file mode 100644 index 0000000..2adddae --- /dev/null +++ b/src/bf_pktpy/library/utils/answer.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python + + +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" Answer module """ + + +# ============================================================================= +class Answer(list): + """Answer container class""" + + def __iter__(self): + for each in self[:]: + yield each + + def is_empty(self): + """Check if container is empty or not""" + return False if self[:] else True + + def summary(self): + """get summary""" + for sent, rcvd in self: + print("%s ==> %s" % (sent.brief(), rcvd.brief())) + + +# ============================================================================= +class Unanswer(list): + """Unanswer container class""" + + def __iter__(self): + for each in self[:]: + yield each + + def is_empty(self): + """Check if container is empty or not""" + return False if self[:] else True + + def summary(self): + """get summary""" + for sent in self: + print(sent.brief()) + + +# ============================================================================= diff --git a/src/bf_pktpy/library/utils/bridge_and_sniff.py b/src/bf_pktpy/library/utils/bridge_and_sniff.py new file mode 100644 index 0000000..97e2594 --- /dev/null +++ b/src/bf_pktpy/library/utils/bridge_and_sniff.py @@ -0,0 +1,162 @@ +#!/usr/bin/env python + + +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" BridgeSniff module """ +import socket +import time +import threading +from bf_pktpy.library.utils.sniff import Sniffer, timer, listen + + +# ============================================================================= +def generate_default_prn(peers, xfrms): + """Generate default prn function""" + + def func(pkt): + try: + sock = peers[pkt.sniffed_on] + except Exception: + return + + proc = xfrms.get(pkt.sniffed_on) + if not proc: + return + try: + new_pkt = proc(pkt) + except Exception: + raise RuntimeError("Transforming pkt using %r failed" % proc.__name__) + + if new_pkt is True: + # if True, will forward packet as is + new_pkt = pkt + elif new_pkt is False: + # the packet is discarded + return + else: + # forward modified packet ('else' here is for clarity) + pass + + try: + if new_pkt.proto in (6, 17): + result = sock.sendto(new_pkt.pack(), (new_pkt.dst, new_pkt.body.dport)) + else: + result = sock.sendto(new_pkt.pack(), (new_pkt.dst, 0)) + if result == 0: + raise IOError("Cannot forward packet ..") + except Exception: + raise IOError("Unable to send successfully") + + return func + + +def generate_prn(prn, peers, xfrms): + """Generate prn function""" + + def func(pkt): + generate_default_prn(peers, xfrms) + return prn(pkt) + + return func + + +class BridgeSniff(Sniffer): + """BridgeSniff class + Examples: + | + """ + + @staticmethod + def bind_socket(iface): + """Bind interace to socket""" + try: + sock = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(3)) + except socket.error: + print("ERROR- Cannot create a socket") + raise + sock.bind((iface, 0)) + return sock + + def __init__(self, if1, if2, *args, **kwargs): + # NOTE(sborkows): Sniffer is in old-class style + Sniffer.__init__(self, *args, **kwargs) + + self.if1 = if1 + self.if2 = if2 + if not isinstance(if1, str): + raise ValueError("Expect for interface name, but got %r" % if1) + sock1 = BridgeSniff.bind_socket(if1) + + if not isinstance(if2, str): + raise ValueError("Expect for interface name, but got %r" % if2) + sock2 = BridgeSniff.bind_socket(if2) + + self.peers = {if1: sock2, if2: sock1} + self.xfrms = {} + xfrm12 = kwargs.pop("xfrm12", None) + xfrm21 = kwargs.pop("xfrm21", None) + if xfrm12: + self.xfrms.update({if1: xfrm12}) + if xfrm21: + self.xfrms.update({if1: xfrm21}) + + def start(self): + """Start sniffing""" + while self._SIG: + # clean up previous signal + self._SIG.pop() + + count, filter_ = self.count, self.filter + + if self.prn is None: + prn_send = generate_default_prn(self.peers, self.xfrms) + else: + prn_send = generate_prn(self.prn, self.peers, self.xfrms) + + # bind to first interface + try: + sock1 = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(3)) + except socket.error: + raise + sock1.bind((self.if1, 0)) + thread1 = threading.Thread( + target=listen, + args=(sock1, self._QUEUE, self._SIG, count, prn_send, filter_), + ) + thread1.start() + + # bind to second interface + try: + sock2 = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(3)) + except socket.error: + raise + sock2.bind((self.if2, 0)) + thread2 = threading.Thread( + target=listen, + args=(sock2, self._QUEUE, self._SIG, count, prn_send, filter_), + ) + thread2.start() + + # start timer if needed + expire_time = 0 + if self.timeout > 0: + expire_time = int(time.time()) + self.timeout + if self._SIG and self._SIG[0] == "quit": + return + thread = threading.Thread(target=timer, args=(self._SIG, expire_time)) + thread.start() + + +# ============================================================================= diff --git a/src/bf_pktpy/library/utils/decoder.py b/src/bf_pktpy/library/utils/decoder.py new file mode 100644 index 0000000..bc22c30 --- /dev/null +++ b/src/bf_pktpy/library/utils/decoder.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python + + +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" Decoder module """ +from bf_pktpy.library.specs.templates.ethernet import Ether +from bf_pktpy.library.specs.templates.ipv4 import IP +from bf_pktpy.library.specs.templates.icmp import ICMP +from bf_pktpy.library.specs.templates.tcp import TCP +from bf_pktpy.library.specs.templates.udp import UDP + + +# ============================================================================= +class Decoder: + """Decoder class""" + + def __init__(self, hex_str): + self.hex_str = hex_str + self.decoded = None + self._stack = "" + self.layer3 = None + self.layer4 = None + if len(self.hex_str) > 28: + self.decoded = Ether.from_hex(self.hex_str[:28]) + if self.decoded and isinstance(self.decoded, Ether): + self._stack = "Ether" + if self.decoded.type_name == "IPv4": + self._stack += " / IPv4" + self.layer3 = IP.from_hex(self.hex_str[28:]) + self.decoded = self.decoded / self.layer3 + offset = self.layer3.ihl * 4 * 2 + if offset % 8: + offset += 8 - offset % 8 # add length of padding + payload = self.hex_str[28 + offset :] + if self.layer3.proto_name == "ICMP": + self._stack += " / ICMP" + self.layer4 = ICMP.from_hex(payload) + self.decoded = self.decoded / self.layer4 + elif self.layer3.proto_name == "TCP": + self._stack += " / TCP" + self.layer4 = TCP.from_hex(payload) + self.decoded = self.decoded / self.layer4 + elif self.layer3.proto_name == "UDP": + self._stack += " / UDP" + self.layer4 = UDP.from_hex(payload) + self.decoded = self.decoded / self.layer4 + else: + # add more protocol as needed + pass + + def __call__(self): + return self.decoded + + def hex(self): + """To hex""" + if self.decoded: + return self.decoded.hex() + return "" + + def bin(self): + """To binary""" + if self.decoded: + return self.decoded.bin() + return "" + + def is_protocol(self, protocol): + """Check if hex string is ethernet type""" + if protocol.lower() in self._stack.lower(): + return True + return False + + def brief(self): + """Short description of the packet""" + descr = self._stack[8:] + " " + if "IPv4" in self._stack or "IPv6" in self._stack: + descr += "%s > %s" % (self.layer3.src, self.layer3.dst) + return descr + + +# ============================================================================= diff --git a/src/bf_pktpy/library/utils/hexdump.py b/src/bf_pktpy/library/utils/hexdump.py new file mode 100644 index 0000000..f69952b --- /dev/null +++ b/src/bf_pktpy/library/utils/hexdump.py @@ -0,0 +1,86 @@ +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import six + + +def hexdump(packet, dump=False): + def to_number(number): + return number if isinstance(number, int) else ord(number) + + def to_char(_number): + j = to_number(_number) + if (j < 32) or (j >= 127): + return "." + return chr(j) + + def make_rows(obj, num): + return [ + obj[start : start + num] + for start in range(0, len(obj), num) + if obj[start : start + num] + ] + + def convert_incoming_packet(): + potential_hex = None + if hasattr(packet, "hex"): # conversion for the 'Base' objects + potential_hex = packet.hex() + elif six.PY2: + if isinstance(packet, str): + if [b for b in packet.split() if len(b) == 2]: + potential_hex = packet.split() + else: + import binascii + + potential_hex = binascii.b2a_hex(packet) + else: # do for the Python 3.5+ + if isinstance(packet, str): + return packet.replace(" ", "") + try: + potential_hex = bytes(packet).hex() + except TypeError: + try: + potential_hex = bytes(packet, encoding="utf-8").hex() + except TypeError: + pass + + if potential_hex is None: + raise TypeError( + "Given packet is of incorrect type: {}".format(type(packet).__name__) + ) + return potential_hex + + if isinstance(packet, list): + packet_hex = [ + (hex(b)[2:].zfill(2) if isinstance(b, int) else b) for b in packet + ] + else: + packet_hex = convert_incoming_packet() + + if not isinstance(packet_hex, list) and " " not in packet_hex: + packet_hex = make_rows(packet_hex, 2) + elif isinstance(packet_hex, str) and " " in packet_hex: + packet_hex = packet_hex.split() + + output = [] + for e, line in enumerate(make_rows(packet_hex, 16)): + console_char = [to_char(int(x, 16)) for x in line] + if len(line) < 16: + line += [" " for _ in range(16 - len(line))] + + output.append( + "%03x0 %s %s" % (e, " ".join(line).upper(), "".join(console_char)) + ) + if dump: + return output + print("\n".join(output)) diff --git a/src/bf_pktpy/library/utils/interface.py b/src/bf_pktpy/library/utils/interface.py new file mode 100644 index 0000000..a41b706 --- /dev/null +++ b/src/bf_pktpy/library/utils/interface.py @@ -0,0 +1,233 @@ +#!/usr/bin/env python + + +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" Interface module """ +import socket +import subprocess +from collections import namedtuple + +import ipaddress +import netifaces +import psutil + +# ============================================================================= +Intf = namedtuple("Intf", "name family address netmask broadcast mac") +Stats = namedtuple("Stats", "isup duplex speed mtu") +Route = namedtuple("Route", "name gateway is_default") +Gateway = namedtuple("Gateway", "name mac address") + + +class Interface: + """Interface class""" + + @staticmethod + def get_interfaces(): + """Get interfaces + + Returns: + list: list[(name, family, address, netmask, broadcast, mac), .] + Examples: + | interfaces = get_interfaces() + | + """ + interfaces, mac = [], "" + for name, nics in psutil.net_if_addrs().items(): + mac = "" + intf = None + for nic in nics: + if nic.family == socket.AF_PACKET: + mac = nic.address + if nic.family == socket.AF_INET: + intf = nic + if intf: + interfaces.append( + Intf( + name, + intf.family, + intf.address, + intf.netmask, + intf.broadcast, + mac, + ) + ) + return interfaces + + @staticmethod + def get_interface_names(): + """Get interface names + + Returns: + list: list[, ...] + Examples: + | names = get_interface_names() + | + """ + interfaces = Interface.get_interfaces() + return [each.name for each in interfaces] + + @staticmethod + def get_interface(name, family=socket.AF_INET): + """Get interface + + Args: + name (str): interface name + family (int): ip address family + Returns: + tuple: (name, family, address, netmask, broadcast, mac) + Examples: + | interface = Interface.get_interface("ens160", socket.AF_INET) + | + """ + interfaces = Interface.get_interfaces() + for each in interfaces: + if each.name == name and each.family == family: + return each + raise ValueError("Unable to find interface %r" % name) + + @staticmethod + def get_stats(name): + """Get interface stats + + Args: + name (str): interface name + Returns: + list: list[(name, type, ipaddr, netmask, broadcast, mac), ...] + Examples: + | stats = Interface.get_stats("ens160") + | + """ + for name_, stats in psutil.net_if_stats().items(): + if name_ == name: + return Stats(stats.isup, stats.duplex, stats.speed, stats.mtu) + return None + + @staticmethod + def get_routes(name=""): + """Get routes + + Args: + name (str): interface name + Returns: + list: list[(name, type, ipaddr, netmask, broadcast, mac), ...] + Examples: + | routes = Interface.get_routes("ens160") + | + """ + routes = [] + gateways = netifaces.gateways() + for _, group in gateways.items(): + is_default = False + if isinstance(group, dict): + each = list(group.values())[0] + if len(each) == 3: + gateway, name_, is_default = each + else: + gateway, name_ = each + if name == name_: + return [Route(name_, gateway, is_default)] + routes.append(Route(name_, gateway, is_default)) + if isinstance(group, list): + for each in group: + is_default = False + if len(each) == 3: + gateway, name_, is_default = each + else: + gateway, name_ = each + if name == name_: + return [Route(name_, gateway, is_default)] + routes.append(Route(name_, gateway, is_default)) + return routes + + @staticmethod + def get_gateway(name=""): + """Get default gateway + + Args: + name (str): interface name + Returns: + tuple: (name, mac, address) or NoneType if not found + Examples: + | gateway = Interface.get_gateway() + | + """ + + def get_gateway_mac(address): + subprocess.call("ping -c 1 %s" % address, shell=True) + proc = subprocess.Popen( + "arp -n -a | grep %s" % address, stdout=subprocess.PIPE, shell=True + ) + output = proc.stdout.read() + for line in output.splitlines(): + text = line.decode("ascii") + words = text.split() + if words[0] == address: + # linux + return words[2] + if words[0] == "?": + # mac + return words[3] + return None + + routes = Interface.get_routes(name) + if name and not len(routes): + raise ValueError("Unable to find gateway for interface %r" % name) + route = routes[0] + try: + address = str(ipaddress.IPv4Address(route.gateway)) + except ValueError: + if name: + raise ValueError("Unable to gw ip for interface %r" % name) + address = "" + if not address: + routes = Interface.get_routes() + for route in routes: + try: + address = str(ipaddress.IPv4Address(route.gateway)) + break + except ValueError: + pass + if not address: + return None + gw_mac = get_gateway_mac(address) + if gw_mac is None: + raise ValueError("Unable to get gateway mac") + return Gateway(route.name, gw_mac, address) + + @staticmethod + def select(name=""): + """Look for interface name. Returns name if name provided + and matched existing interface names. Else, returns + default interface name + + Args: + name (str): interface name + Returns: + str: interface name + Examples: + | intf_name = Interface.select("ens160") + | + """ + if name: + if name in Interface.get_interface_names(): + return name + raise ValueError("Interface name %r not found" % name) + for each in Interface.get_routes(): + if each.is_default: + return each.name + raise ValueError("Unable to find default interface") + + +# ============================================================================= diff --git a/src/bf_pktpy/library/utils/listener.py b/src/bf_pktpy/library/utils/listener.py new file mode 100644 index 0000000..e8d2d7a --- /dev/null +++ b/src/bf_pktpy/library/utils/listener.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python + + +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" Listener module """ +import binascii +import socket +import threading + +try: + import queue +except ImportError: + # for Python 2 support + import Queue as queue + + +# ============================================================================= +def listen(socket, queue_, sig_, bufsize=1024): + """Listen worker + + Args: + socket (obj): socket object + queue_ (obj): queue to stored received pkts + sig_ (obj): signal queue to stop thread + bufsize (int): buffer size + Returns: + str: received data + Examples: + | data = listen(, ...) + | + """ + + while True: + if sig_.qsize(): + signal = sig_.get() + if signal == "quit": + break + data = socket.recv(bufsize) + temp = str(binascii.hexlify(data)) + queue_.put(temp[2:-1]) + + +class Listener: + """Listener class + + Examples: + | + """ + + _QUEUE = queue.Queue() + _SIG = queue.Queue() + + def __init__(self, interface, **kwargs): + """ + Args: + interface (obj): interface object + Returns: + stream: stream object + Examples: + | listener = Listener(**kwargs) + | + """ + self.interface = interface + self.timeout = kwargs.pop("timeout", 0) + self.inter = kwargs.pop("inter", 0) + self.verbose = kwargs.pop("verbose", False) + + def start(self, count=1): + """Start listening""" + + try: + sock = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(3)) + except socket.error: + raise + sock.bind((self.interface, 0)) + + thread = threading.Thread( + target=listen, args=(sock, Listener._QUEUE, Listener._SIG) + ) + thread.start() + + @staticmethod + def received(): + """Get received packets""" + + packets = [] + while Listener._QUEUE.qsize(): + packets.append(Listener._QUEUE.get()) + Listener._SIG.put("quit") + return packets + + +# ============================================================================= diff --git a/src/bf_pktpy/library/utils/ls.py b/src/bf_pktpy/library/utils/ls.py new file mode 100644 index 0000000..8c3e87f --- /dev/null +++ b/src/bf_pktpy/library/utils/ls.py @@ -0,0 +1,151 @@ +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import six +import tabulate + +from bf_pktpy.packets import Base, Packet + + +def ls(pkt=None): + if pkt is None: + _print_all_headers_info() + return + + if not isinstance(pkt, Base): + raise ValueError( + "Given argument is not of neither Base nor Packet type: %s" % type(pkt) + ) + + lines_to_print = [] + current_layer = pkt + while current_layer is not None: + lines_to_print.append( + "-- %s" % current_layer.name + if hasattr(current_layer, "name") + else "-- Payload" + ) + if isinstance(current_layer, Packet): + lines_to_print += _ls_packet(current_layer) + elif isinstance(current_layer, Base): + lines_to_print += _ls_base(current_layer) + elif isinstance( + current_layer, (six.string_types, six.text_type, six.binary_type) + ): + lines_to_print.append("load : %s" % current_layer) + else: + raise ValueError( + "Given packet contains layer of invalid type: %s" % type(current_layer) + ) + + current_layer = current_layer.body if hasattr(current_layer, "body") else None + + for line in lines_to_print: + print(line) + + +def _print_all_headers_info(): + all_modules = __recursive_subclass_lookup(Base) + filtered_modules = [ + module for module in all_modules if not __module_in_blacklist(module) + ] + + modules_for_print = [ + [module.__qualname__, module.name] for module in filtered_modules + ] + print( + tabulate.tabulate(modules_for_print, headers=["class", "name"], tablefmt="psql") + ) + + +def __recursive_subclass_lookup(parent_module): + my_headers = [] + for direct_subclass in parent_module.__subclasses__(): + my_headers.append(direct_subclass) + my_headers.extend(__recursive_subclass_lookup(direct_subclass)) + + return my_headers + + +def __module_in_blacklist(module_to_check): + modules_blacklist = [ + "bf_pktpy.library.specs.packet", + "bf_pktpy.library.specs.templates.ipoption", + "bf_pktpy.library.specs.templates.raw", + "bf_pktpy.library.specs.templates.tcpoption", + ] + return any( + blacklisted_module in module_to_check.__module__ + for blacklisted_module in modules_blacklist + ) + + +def _ls_base(layer): + # noinspection PyProtectedMember + members = list(layer._members().values())[0] + unzipped_members = list(zip(*members)) + name_offset, val_offset, size_offset = ( + max(len(str(item)) for item in field_type_tuple) + 1 + for field_type_tuple in unzipped_members + ) + + lines = [] + for name, value, size in members: + lines.append( + "%s%s: %d (bits)%s= %s" + % ( + name, + " " * (name_offset - len(name)), + size, + " " * (size_offset - len(str(size))), + str(value), + ) + ) + + return lines + + +def _ls_packet(layer): + members = [ + ( + field.name, + type(field).__name__, + field.size(layer) if callable(field.size) else field.size, + getattr(layer, field.name), + field.default_value(layer) + if callable(field.default_value) + else field.default_value, + ) + for field in layer.fields_desc + ] + unzipped_members = list(zip(*members)) + name_offset, type_offset, size_offset, val_offset, _ = ( + max(len(str(item)) for item in field_type_tuple) + 1 + for field_type_tuple in unzipped_members + ) + + lines = [] + for name, type_name, size, value, default_value in members: + lines.append( + "%s%s: %s (%d bits)%s= %s%s (%s)" + % ( + name, + " " * (name_offset - len(name)), + type_name, + size, + " " * (type_offset + size_offset - len(type_name) - len(str(size))), + value, + " " * (val_offset - len(str(value))), + default_value, + ) + ) + + return lines diff --git a/src/bf_pktpy/library/utils/sniff.py b/src/bf_pktpy/library/utils/sniff.py new file mode 100644 index 0000000..c4d179e --- /dev/null +++ b/src/bf_pktpy/library/utils/sniff.py @@ -0,0 +1,167 @@ +#!/usr/bin/env python + + +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" Sniffer module """ +import binascii +from six.moves import queue +import socket +import threading +import time +import types + +from bf_pktpy.library.utils import Decoder + + +# ============================================================================= +class Received(list): + """Received class + Examples: + | + """ + + def __iter__(self): + for each in self[:]: + yield each + + def is_empty(self): + """Check if container is empty or not""" + return False if self[:] else True + + def summary(self): + """get summary""" + for pkt in self: + print("%s" % pkt.brief()) + + +def listen(socket, queue_, sig_, count, prn, filter_="ip", bufsize=1024): + """Listen worker + Args: + socket (obj): socket object + queue_ (obj): queue to stored received pkts + sig_ (obj): signal queue to stop thread + count (int): number of packets to capture, 0: infinity + prn (obj): function to apply to each packet + filter_ (str): protocol name to look for + bufsize (int): buffer size + Returns: + None + Examples: + | listen(, ..) + | + """ + + while True: + if sig_ and sig_[0] == "quit": + return + data = socket.recv(bufsize) + raw = str(binascii.hexlify(data)) + decoded = Decoder(raw[2:-1]) + if decoded.is_protocol(filter_): + if isinstance(prn, types.FunctionType): + prn(decoded) + queue_.put(decoded) + if count > 0 and count == queue_.qsize(): + sig_.append("quit") + + +def timer(sig_, expire_time): + """Send quit signal when time is expired + Args: + expire_time (int): stop at given time, 0: no expire + """ + + while True: + if sig_ and sig_[0] == "quit": + return + if expire_time <= int(time.time()): + sig_.append("quit") + time.sleep(1) + + +class Sniffer: + """Sniffer class + Examples: + | + + Note: this class is a parent of BridgeSniff. Change here can affect + the behavior of derived class + """ + + def __init__(self, *args, **kwargs): + self._QUEUE = queue.Queue() + self._SIG = [] + self.filter = kwargs.pop("filter", "ip") # BPF filter to apply + iface = kwargs.pop("iface", []) # 1 or more interfaces + if iface and isinstance(iface, str): + iface = [iface] + if not isinstance(iface, (list, tuple, dict)): + raise ValueError("Unsupported type for iface %r" % type(iface)) + self.interfaces = iface + + # function applied to each pkt + self.prn = kwargs.pop("prn", None) + self.count = kwargs.pop("count", 0) # 0 means infinity + self.timeout = kwargs.pop("timeout", 0) # expire time + self.store = kwargs.pop("store", True) # store or discard pkts + + def start(self): + """Start sniffing""" + while self._SIG: + # clean up previous signal + self._SIG.pop() + + count, prn, filter_ = self.count, self.prn, self.filter + # bind to one or more interfaces + for iface in self.interfaces: + try: + sock = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(3)) + except socket.error: + raise + sock.bind((iface, 0)) + thread = threading.Thread( + target=listen, args=(sock, self._QUEUE, self._SIG, count, prn, filter_) + ) + thread.start() + + # start timer if needed + expire_time = 0 + if self.timeout > 0: + expire_time = int(time.time()) + self.timeout + if self._SIG and self._SIG[0] == "quit": + return + thread = threading.Thread(target=timer, args=(self._SIG, expire_time)) + thread.start() + + def is_completed(self): + """Check if sniff packets is done""" + if self._SIG and self._SIG[0] == "quit": + return True + return False + + def stop(self): + """Stop sniffing""" + self._SIG.append("quit") + + def received(self): + """Get received packets""" + packets = [] + while self._QUEUE.qsize(): + packets.append(self._QUEUE.get()) + self._SIG.append("quit") + return packets + + +# ============================================================================= diff --git a/src/bf_pktpy/library/utils/stream.py b/src/bf_pktpy/library/utils/stream.py new file mode 100644 index 0000000..60de1b6 --- /dev/null +++ b/src/bf_pktpy/library/utils/stream.py @@ -0,0 +1,153 @@ +#!/usr/bin/env python + + +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" Stream module """ +import time + +from bf_pktpy.library.utils.interface import Interface + + +# ============================================================================= +class Stream: + """Stream class + + Examples: + | stream = Stream(packets, interface, layer) + """ + + def __init__(self, packets, interface): + """ + Args: + packets (list): a list of packet objects + interface (obj): interface object + Returns: + stream: stream object + Examples: + | stream = Stream(packets, interface) + """ + self.packets = packets + self.interface = interface + self.layer = 2 + intf = None + for packet in self.packets: + if intf is None: + intf = Interface.get_interface(interface) + # needed by bridge_and_sniff function + packet.sniffed_on = intf + if hasattr(packet, "name") and packet.name in ("IP", "IPv6"): + # layer 3 + self.layer = 3 + packet.src = intf.address + # check if TCP + if hasattr(packet.body, "name") and packet.body.name == "TCP": + # if flags field is not provided, then set SYN bit on + tcp = packet.body + if tcp.flags == 0: + # set TCP SYN bit on like scapy + tcp.flags = 2 + # recalculate l4 checksum + packet.update_l4_checksum() + else: + # layer 2 + if hasattr(packet.body, "name"): + body = packet.body + if body.name == "IP" or body.name == "IPv6": + # create layer2 if not existed + type_ = 0x0800 + if packet.body.name == "IPv6": + type_ = 0x86DD + gateway = Interface.get_gateway(interface) + packet.src = intf.mac + packet.dst = gateway.mac + packet.type = type_ + if not packet.body.src: + # fill src ip if not provided + packet.body.src = intf.address + # recalculate l4 checksum + packet.update_l4_checksum() + + def __repr__(self): + string = "" + for packet in self.packets: + string = packet.__repr__() + "\n" + return string.strip() + + def send(self, sock, **kwargs): + """Send stream out of a socket + + Args: + sock (obj): socket object + inter (int): time in sec between 2 packets (def: 0) + loop (int): send packet indefinetly (default 0) + count (int): number of packets to send (default -1) + verbose (int): verbose mode + realtime (int): check pkt was sent before send next one + return_packets (bool): return the sent packets + Returns: + bool: true if success + Examples: + | result = self.send(sock) + """ + inter = kwargs.pop("inter", 0) + loop = kwargs.pop("loop", 0) + count = kwargs.pop("count", -1) + verbose = kwargs.pop("verbose", False) + + # Not implement realtime for now + _ = kwargs.pop("realtime", None) + + result = None + try: + # 'count' takes precedence over 'loop' + if count == -1: + if loop: + count, loop = 1, 1 + else: + count, loop = 1, 0 + else: + loop = 0 + while count > 0: + if verbose: + print("Sending packet") + if self.layer == 3: + for packet in self.packets: + if packet.proto in (6, 17): + result = sock.sendto( + packet.pack(), (packet.dst, packet.body.dport) + ) + else: + result = sock.sendto(packet.pack(), (packet.dst, 0)) + # returns the number of bytes sent + if result == 0: + raise IOError("Unable to send successfully") + else: + # layer-2 + for packet in self.packets: + result = sock.sendall(packet.pack()) + # None is returned on success + if result is not None: + raise IOError("Unable to send successfully") + time.sleep(inter) + count = count + loop - 1 + except KeyboardInterrupt: + print("KeyboardInterrupt") + + if kwargs.get("return_packets"): + return result + return None + + +# ============================================================================= diff --git a/src/bf_pktpy/library/utils/tool.py b/src/bf_pktpy/library/utils/tool.py new file mode 100644 index 0000000..d731f79 --- /dev/null +++ b/src/bf_pktpy/library/utils/tool.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python + + +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" bf_pktpy python library """ +import socket + + +# ============================================================================= +class Tool: + """Tool collection""" + + @staticmethod + def get_ipaddr_from_dname(dname, port=80): + """Return IP of domain name + + Args: + dname (str): domain name + port (int): port number + Returns: + str: ip address + Raises: + socket.gaierror + Examples: + | addr = Tool.get_ipaddr_from_dname("google.com") + | + """ + try: + result = socket.getaddrinfo(dname, port) + return result[0][4][0] + except socket.gaierror: + raise + + +# ============================================================================= diff --git a/src/bf_pktpy/library/validators/__init__.py b/src/bf_pktpy/library/validators/__init__.py new file mode 100644 index 0000000..5f7310a --- /dev/null +++ b/src/bf_pktpy/library/validators/__init__.py @@ -0,0 +1,11 @@ +import warnings + + +warnings.warn( + '"validators" has been renamed into "fields" and now is deprecated, please ' + 'change imports to use "fields" in imports.', + DeprecationWarning, +) + +# noinspection PyUnresolvedReferences +from bf_pktpy.library.fields import * diff --git a/src/bf_pktpy/main.py b/src/bf_pktpy/main.py new file mode 100644 index 0000000..e09dadf --- /dev/null +++ b/src/bf_pktpy/main.py @@ -0,0 +1,20 @@ +# Copyright (c) 2022 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from six import print_ + + +if __name__ == "__main__": + print_( + "Interactive console has been disabled, due to dropping 'ipython' as SDE " + "dependency." + ) diff --git a/src/bf_pktpy/packets/__init__.py b/src/bf_pktpy/packets/__init__.py new file mode 100644 index 0000000..7457a91 --- /dev/null +++ b/src/bf_pktpy/packets/__init__.py @@ -0,0 +1,35 @@ +from bf_pktpy.library.specs.base import Base +from bf_pktpy.library.specs.packet import Packet + +from bf_pktpy.library.specs.templates.ethernet import Ether +from bf_pktpy.library.specs.templates.arp import ARP +from bf_pktpy.library.specs.templates.bfd import BFD +from bf_pktpy.library.specs.templates.ipv4 import IP +from bf_pktpy.library.specs.templates.ipv6 import IPv6 +from bf_pktpy.library.specs.templates.udp import UDP +from bf_pktpy.library.specs.templates.tcp import TCP +from bf_pktpy.library.specs.templates.dot1q import Dot1Q +from bf_pktpy.library.specs.templates.dot1ad import Dot1AD +from bf_pktpy.library.specs.templates.icmp import ICMP +from bf_pktpy.library.specs.templates.igmp import IGMP +from bf_pktpy.library.specs.templates.icmpv6_unknown import ICMPv6Unknown +from bf_pktpy.library.specs.templates.bootp import BOOTP +from bf_pktpy.library.specs.templates.dhcp import DHCP +from bf_pktpy.library.specs.templates.vxlan import VXLAN +from bf_pktpy.library.specs.templates.erspan import ERSPAN +from bf_pktpy.library.specs.templates.erspan import ERSPAN_II +from bf_pktpy.library.specs.templates.erspan import ERSPAN_III +from bf_pktpy.library.specs.templates.erspan import ERSPAN_PlatformSpecific +from bf_pktpy.library.specs.templates.gre import GRE +from bf_pktpy.library.specs.templates.cpu import * +from bf_pktpy.library.specs.templates.ipoption import * +from bf_pktpy.library.specs.templates.tcpoption import TCPOptionPlaceholder +from bf_pktpy.library.specs.templates.mpls import MPLS +from bf_pktpy.library.specs.templates.ipv6_ext_hdr_routing import IPv6ExtHdrRouting +from bf_pktpy.library.specs.templates.gtpu import GTPU +from bf_pktpy.library.specs.templates.raw import Raw +from bf_pktpy.library.specs.templates.cpu.simple_l3_mirror_cpu_header import ( + SimpleL3SwitchCpuHeader, +) +from bf_pktpy.library.specs.templates.sfc import * +from bf_pktpy.library.specs.templates.xnt import * diff --git a/src/bf_pktpy/ptf/__init__.py b/src/bf_pktpy/ptf/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/bf_pktpy/ptf/packet_pktpy.py b/src/bf_pktpy/ptf/packet_pktpy.py new file mode 100644 index 0000000..e8e00b0 --- /dev/null +++ b/src/bf_pktpy/ptf/packet_pktpy.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python + + +# Copyright (c) 2021 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +""" +Bf_pktpy implementation of packet manipulation module. For more information, +see PTF documentation (section "Pluggable packet manipulation module"). +""" +import bf_pktpy.packets +from bf_pktpy.all import hexdump as bf_pktpy_hexdump, ls as bf_pktpy_ls +from ptf import config + + +# Headers set to None are not yet implemented (or conditionally being set) +Packet = bf_pktpy.packets.Packet +Ether = bf_pktpy.packets.Ether +LLC = None +SNAP = None +Dot1Q = bf_pktpy.packets.Dot1Q +GRE = bf_pktpy.packets.GRE +IP = bf_pktpy.packets.IP +IPOption = bf_pktpy.packets.IPOptionPlaceholder +ARP = bf_pktpy.packets.ARP +TCP = bf_pktpy.packets.TCP +UDP = bf_pktpy.packets.UDP +ICMP = bf_pktpy.packets.ICMP +DHCP = bf_pktpy.packets.DHCP +BOOTP = bf_pktpy.packets.BOOTP +PADDING = None +VXLAN = bf_pktpy.packets.VXLAN +BTH = None + +IPv6 = None +IPv6ExtHdrRouting = None +ICMPv6Unknown = None +ICMPv6EchoRequest = None +ICMPv6MLReport = None +if not config.get("disable_ipv6", False): + IPv6 = bf_pktpy.packets.IPv6 + IPv6ExtHdrRouting = bf_pktpy.packets.IPv6ExtHdrRouting + ICMPv6Unknown = bf_pktpy.packets.ICMPv6Unknown + +ERSPAN = None +ERSPAN_III = None +PlatformSpecific = None +if not config.get("disable_erspan", False): + try: + ERSPAN = bf_pktpy.packets.ERSPAN + ERSPAN_III = bf_pktpy.packets.ERSPAN_III + PlatformSpecific = bf_pktpy.packets.ERSPAN_PlatformSpecific + except ImportError as e: + print("ERSPAN support not found in bf_pktpy. Details:\n%s" % e) + +GENEVE = None + +MPLS = None +if not config.get("disable_mpls", False): + MPLS = bf_pktpy.packets.MPLS + +NVGRE = None + +IGMP = None +if not config.get("disable_igmp", False): + try: + IGMP = bf_pktpy.packets.IGMP + except ImportError as e: + print("IGMP support not found in bf_pktpy. Details:\n%s" % e) + + +############################################################################## + +# Headers implemented, but not in Scapy version of packet module +SimpleL3SwitchCpuHeader = bf_pktpy.packets.SimpleL3SwitchCpuHeader +BFD = bf_pktpy.packets.BFD +TCPOption = bf_pktpy.packets.TCPOptionPlaceholder +MirrorPreDeparser = bf_pktpy.packets.MirrorPreDeparser +GTPU = bf_pktpy.packets.GTPU + +XntIntMeta = bf_pktpy.packets.XntIntMeta +XntIntL45Head = bf_pktpy.packets.XntIntL45Head +XntIntL45Tail = bf_pktpy.packets.XntIntL45Tail + + +def get_erspan_alternative(): + """ + Return ERSPAN alternative implementation + Example usage: + ERSPAN, ERSPAN_III, PlatformSpecific = get_erspan_alternative() + :return: + """ + if config.get("disable_erspan", False): + return (None,) * 3 + + from bf_pktpy.library.specs.templates.erspan.alternative.erspan import ( + ERSPAN as alt_ERSPAN, + ) + from bf_pktpy.library.specs.templates.erspan.alternative.erspan_iii import ( + ERSPAN_III as alt_ERSPAN_III, + ) + from bf_pktpy.library.specs.templates.erspan.alternative.platform_specific import ( + PlatformSpecific as alt_PlatformSpecific, + ) + + return alt_ERSPAN, alt_ERSPAN_III, alt_PlatformSpecific + + +# bf_pktpy implementation of hexdump +hexdump = bf_pktpy_hexdump +ls = bf_pktpy_ls From 6d110ce5d7407fec5e57dc8be84424061743fbc2 Mon Sep 17 00:00:00 2001 From: Andy Fingerhut Date: Fri, 14 Mar 2025 15:25:30 +0000 Subject: [PATCH 2/5] Add Python package dependencies for bf_pktpy to setup.cfg so that when ptf is installed via `pip install ptf`, all of those other Python packages will also be installed. Signed-off-by: Andy Fingerhut --- setup.cfg | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/setup.cfg b/setup.cfg index fab4659..8988a07 100644 --- a/setup.cfg +++ b/setup.cfg @@ -22,6 +22,12 @@ platforms = any python_requires = >=3.4 setup_requires = setuptools_scm +install_requires = + six + getmac + scapy_helper + netifaces + psutil [options.packages.find] where = src From a35379221a1c1b03bc404f9c447db42d61d4da9c Mon Sep 17 00:00:00 2001 From: Andy Fingerhut Date: Fri, 14 Mar 2025 15:29:26 +0000 Subject: [PATCH 3/5] Formatting changes from running black on bf_pktpy code Signed-off-by: Andy Fingerhut --- src/bf_pktpy/commands.py | 2 +- src/bf_pktpy/library/fields/dest_ip_field.py | 2 +- src/bf_pktpy/library/fields/enum_field.py | 2 +- src/bf_pktpy/library/fields/field.py | 8 +++++--- src/bf_pktpy/library/fields/flag_value.py | 2 +- src/bf_pktpy/library/fields/ip_field.py | 2 +- src/bf_pktpy/library/fields/mac_field.py | 2 +- src/bf_pktpy/library/fields/source_ip_field.py | 2 +- src/bf_pktpy/library/fields/x_bit_field.py | 8 +++++--- src/bf_pktpy/library/helpers/ip.py | 2 +- src/bf_pktpy/library/specs/base.py | 2 +- src/bf_pktpy/library/specs/bfd.py | 2 +- src/bf_pktpy/library/specs/bootp.py | 2 +- src/bf_pktpy/library/specs/constant.py | 2 +- src/bf_pktpy/library/specs/dhcp.py | 2 +- src/bf_pktpy/library/specs/dot1q.py | 2 +- src/bf_pktpy/library/specs/ethernet.py | 2 +- src/bf_pktpy/library/specs/gre.py | 2 +- src/bf_pktpy/library/specs/icmp.py | 2 +- src/bf_pktpy/library/specs/ipv4.py | 2 +- src/bf_pktpy/library/specs/ipv6.py | 2 +- src/bf_pktpy/library/specs/packet.py | 2 +- src/bf_pktpy/library/specs/pretty.py | 2 +- src/bf_pktpy/library/specs/tcp.py | 2 +- src/bf_pktpy/library/specs/templates/arp.py | 2 +- src/bf_pktpy/library/specs/templates/bfd.py | 2 +- src/bf_pktpy/library/specs/templates/bootp.py | 2 +- src/bf_pktpy/library/specs/templates/control.py | 2 +- .../library/specs/templates/cpu/dtel_report_hdr.py | 2 +- .../library/specs/templates/cpu/dtel_report_v2_hdr.py | 2 +- .../specs/templates/cpu/fabric_cpu_bfd_event_header.py | 2 +- .../library/specs/templates/cpu/fabric_cpu_header.py | 2 +- .../specs/templates/cpu/fabric_cpu_sflow_header.py | 2 +- .../specs/templates/cpu/fabric_cpu_timestamp_header.py | 2 +- src/bf_pktpy/library/specs/templates/cpu/fabric_header.py | 2 +- .../specs/templates/cpu/fabric_multicast_header.py | 2 +- .../library/specs/templates/cpu/fabric_payload_header.py | 2 +- .../library/specs/templates/cpu/fabric_unicast_header.py | 2 +- src/bf_pktpy/library/specs/templates/cpu/mod_header.py | 2 +- .../library/specs/templates/cpu/postcard_header.py | 2 +- .../specs/templates/cpu/simple_l3_mirror_cpu_header.py | 2 +- src/bf_pktpy/library/specs/templates/dhcp.py | 2 +- src/bf_pktpy/library/specs/templates/dot1ad.py | 2 +- src/bf_pktpy/library/specs/templates/dot1q.py | 2 +- .../library/specs/templates/erspan/alternative/erspan.py | 2 +- .../specs/templates/erspan/alternative/erspan_iii.py | 2 +- .../templates/erspan/alternative/platform_specific.py | 2 +- src/bf_pktpy/library/specs/templates/erspan/erspan.py | 2 +- src/bf_pktpy/library/specs/templates/erspan/erspan_ii.py | 2 +- src/bf_pktpy/library/specs/templates/erspan/erspan_iii.py | 2 +- .../specs/templates/erspan/erspan_platform_specific.py | 2 +- src/bf_pktpy/library/specs/templates/ethernet.py | 2 +- src/bf_pktpy/library/specs/templates/frame.py | 2 +- src/bf_pktpy/library/specs/templates/gre.py | 2 +- src/bf_pktpy/library/specs/templates/icmp.py | 2 +- src/bf_pktpy/library/specs/templates/icmpv6_unknown.py | 2 +- src/bf_pktpy/library/specs/templates/igmp.py | 2 +- src/bf_pktpy/library/specs/templates/ipoption.py | 2 +- src/bf_pktpy/library/specs/templates/ipv4.py | 2 +- src/bf_pktpy/library/specs/templates/ipv6.py | 2 +- .../library/specs/templates/ipv6_ext_hdr_routing.py | 2 +- src/bf_pktpy/library/specs/templates/mpls.py | 2 +- src/bf_pktpy/library/specs/templates/payload.py | 2 +- src/bf_pktpy/library/specs/templates/sfc/sfc_roce.py | 2 +- src/bf_pktpy/library/specs/templates/tcp.py | 2 +- src/bf_pktpy/library/specs/templates/tcpoption.py | 2 +- src/bf_pktpy/library/specs/templates/udp.py | 2 +- src/bf_pktpy/library/specs/templates/vxlan.py | 2 +- src/bf_pktpy/library/specs/udp.py | 2 +- src/bf_pktpy/library/specs/validate.py | 2 +- src/bf_pktpy/library/utils/answer.py | 2 +- src/bf_pktpy/library/utils/bridge_and_sniff.py | 2 +- src/bf_pktpy/library/utils/decoder.py | 2 +- src/bf_pktpy/library/utils/interface.py | 2 +- src/bf_pktpy/library/utils/listener.py | 2 +- src/bf_pktpy/library/utils/ls.py | 8 +++++--- src/bf_pktpy/library/utils/sniff.py | 2 +- src/bf_pktpy/library/utils/stream.py | 2 +- src/bf_pktpy/library/utils/tool.py | 2 +- 79 files changed, 91 insertions(+), 85 deletions(-) diff --git a/src/bf_pktpy/commands.py b/src/bf_pktpy/commands.py index b403aec..542ac3f 100644 --- a/src/bf_pktpy/commands.py +++ b/src/bf_pktpy/commands.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" bf_pktpy python library """ +"""bf_pktpy python library""" import socket import time from bf_pktpy.library.utils import Interface, Stream, Listener, Decoder diff --git a/src/bf_pktpy/library/fields/dest_ip_field.py b/src/bf_pktpy/library/fields/dest_ip_field.py index d859078..bb068b4 100644 --- a/src/bf_pktpy/library/fields/dest_ip_field.py +++ b/src/bf_pktpy/library/fields/dest_ip_field.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" DestIPField """ +"""DestIPField""" from bf_pktpy.library.fields.ip_field import IPField diff --git a/src/bf_pktpy/library/fields/enum_field.py b/src/bf_pktpy/library/fields/enum_field.py index eb4192c..922134f 100644 --- a/src/bf_pktpy/library/fields/enum_field.py +++ b/src/bf_pktpy/library/fields/enum_field.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" EnumField """ +"""EnumField""" import math from operator import attrgetter import six diff --git a/src/bf_pktpy/library/fields/field.py b/src/bf_pktpy/library/fields/field.py index f623194..325b4e1 100644 --- a/src/bf_pktpy/library/fields/field.py +++ b/src/bf_pktpy/library/fields/field.py @@ -41,9 +41,11 @@ def __repr__(self): return "{}(name={}, default_value={}, length={})".format( type(self).__name__, self.name, - self._default_value.__name__ - if callable(self._default_value) - else self.default_value, + ( + self._default_value.__name__ + if callable(self._default_value) + else self.default_value + ), self.size, ) diff --git a/src/bf_pktpy/library/fields/flag_value.py b/src/bf_pktpy/library/fields/flag_value.py index 89f8be3..500ea14 100644 --- a/src/bf_pktpy/library/fields/flag_value.py +++ b/src/bf_pktpy/library/fields/flag_value.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" FlagValue class """ +"""FlagValue class""" class FlagValue(object): diff --git a/src/bf_pktpy/library/fields/ip_field.py b/src/bf_pktpy/library/fields/ip_field.py index 108e00c..0b29edb 100644 --- a/src/bf_pktpy/library/fields/ip_field.py +++ b/src/bf_pktpy/library/fields/ip_field.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" IPField """ +"""IPField""" import ipaddress import six import socket diff --git a/src/bf_pktpy/library/fields/mac_field.py b/src/bf_pktpy/library/fields/mac_field.py index 264c46e..b6df8e3 100644 --- a/src/bf_pktpy/library/fields/mac_field.py +++ b/src/bf_pktpy/library/fields/mac_field.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" MACField """ +"""MACField""" import six from bf_pktpy.library.helpers.mac import correct_mac diff --git a/src/bf_pktpy/library/fields/source_ip_field.py b/src/bf_pktpy/library/fields/source_ip_field.py index 4970417..06ce0ca 100644 --- a/src/bf_pktpy/library/fields/source_ip_field.py +++ b/src/bf_pktpy/library/fields/source_ip_field.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" SourceIPField """ +"""SourceIPField""" from bf_pktpy.library.helpers.ip import get_src_ip_addr from bf_pktpy.library.fields.ip_field import IPField diff --git a/src/bf_pktpy/library/fields/x_bit_field.py b/src/bf_pktpy/library/fields/x_bit_field.py index b1bc0ad..4042bd6 100644 --- a/src/bf_pktpy/library/fields/x_bit_field.py +++ b/src/bf_pktpy/library/fields/x_bit_field.py @@ -10,8 +10,10 @@ def __repr__(self): return "{}(name={}, default_value={}, length={})".format( type(self).__name__, self.name, - self.default_value - if self.default_value is None - else hex(self.default_value), + ( + self.default_value + if self.default_value is None + else hex(self.default_value) + ), self.size, ) diff --git a/src/bf_pktpy/library/helpers/ip.py b/src/bf_pktpy/library/helpers/ip.py index 713faeb..3a48d9f 100644 --- a/src/bf_pktpy/library/helpers/ip.py +++ b/src/bf_pktpy/library/helpers/ip.py @@ -11,7 +11,7 @@ # limitations under the License. ############################################################################### -""" IP helpers """ +"""IP helpers""" import platform import re import six diff --git a/src/bf_pktpy/library/specs/base.py b/src/bf_pktpy/library/specs/base.py index dab5703..b21ec4a 100644 --- a/src/bf_pktpy/library/specs/base.py +++ b/src/bf_pktpy/library/specs/base.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" Base class """ +"""Base class""" import abc import binascii from collections import OrderedDict diff --git a/src/bf_pktpy/library/specs/bfd.py b/src/bf_pktpy/library/specs/bfd.py index 4ba371b..ecbe806 100644 --- a/src/bf_pktpy/library/specs/bfd.py +++ b/src/bf_pktpy/library/specs/bfd.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" BFD class """ +"""BFD class""" from bf_pktpy.library.specs.container import Container from bf_pktpy.library.specs.templates import BFDTemplate diff --git a/src/bf_pktpy/library/specs/bootp.py b/src/bf_pktpy/library/specs/bootp.py index f9a805a..d3301f7 100644 --- a/src/bf_pktpy/library/specs/bootp.py +++ b/src/bf_pktpy/library/specs/bootp.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" BOOTP class """ +"""BOOTP class""" from bf_pktpy.library.specs.container import Container from bf_pktpy.library.specs.templates.bootp import BOOTP as BOOTPTemplate diff --git a/src/bf_pktpy/library/specs/constant.py b/src/bf_pktpy/library/specs/constant.py index 1092d87..af3815d 100644 --- a/src/bf_pktpy/library/specs/constant.py +++ b/src/bf_pktpy/library/specs/constant.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" Define constants """ +"""Define constants""" IP_PATTERN = "^([0-9][0-9]{0,2}\\.){3}[0-9][0-9]{0,2}$" MASK_PATTERN = "^255\\.255\\.[0-9][0-9]{0,2}\\.0$" MAC_PATTERN = "^([a-z0-9]{2}:){5}[a-z0-9]{2}$" diff --git a/src/bf_pktpy/library/specs/dhcp.py b/src/bf_pktpy/library/specs/dhcp.py index 22254ca..9497160 100644 --- a/src/bf_pktpy/library/specs/dhcp.py +++ b/src/bf_pktpy/library/specs/dhcp.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" DHCP class """ +"""DHCP class""" from bf_pktpy.library.specs.container import Container from bf_pktpy.library.specs.templates.dhcp import DHCP as DHCPTemplate diff --git a/src/bf_pktpy/library/specs/dot1q.py b/src/bf_pktpy/library/specs/dot1q.py index 2f8d8a6..6d1a9e0 100644 --- a/src/bf_pktpy/library/specs/dot1q.py +++ b/src/bf_pktpy/library/specs/dot1q.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" Dot1Q class """ +"""Dot1Q class""" from bf_pktpy.library.specs.container import Container from bf_pktpy.library.specs.templates.dot1q import Dot1Q as Dot1QTemplate diff --git a/src/bf_pktpy/library/specs/ethernet.py b/src/bf_pktpy/library/specs/ethernet.py index 4d7a7e0..6328431 100644 --- a/src/bf_pktpy/library/specs/ethernet.py +++ b/src/bf_pktpy/library/specs/ethernet.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" Ether class """ +"""Ether class""" from bf_pktpy.library.specs.container import Container from bf_pktpy.library.specs.templates import clone from bf_pktpy.library.specs.templates.ethernet import Ether as EtherTemplate diff --git a/src/bf_pktpy/library/specs/gre.py b/src/bf_pktpy/library/specs/gre.py index 38452a8..8b10728 100644 --- a/src/bf_pktpy/library/specs/gre.py +++ b/src/bf_pktpy/library/specs/gre.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" GRE class """ +"""GRE class""" from bf_pktpy.library.specs.container import Container from bf_pktpy.library.specs.templates.gre import GRE as GRETemplate diff --git a/src/bf_pktpy/library/specs/icmp.py b/src/bf_pktpy/library/specs/icmp.py index 31e9378..cf27741 100644 --- a/src/bf_pktpy/library/specs/icmp.py +++ b/src/bf_pktpy/library/specs/icmp.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" ICMP class """ +"""ICMP class""" from bf_pktpy.library.specs.container import Container from bf_pktpy.library.specs.templates.icmp import ICMP as ICMPTemplate diff --git a/src/bf_pktpy/library/specs/ipv4.py b/src/bf_pktpy/library/specs/ipv4.py index 5bc37c7..e6f1c82 100644 --- a/src/bf_pktpy/library/specs/ipv4.py +++ b/src/bf_pktpy/library/specs/ipv4.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" IP class """ +"""IP class""" from bf_pktpy.library.specs.container import Container from bf_pktpy.library.specs.templates.ipv4 import IP as IPv4Template diff --git a/src/bf_pktpy/library/specs/ipv6.py b/src/bf_pktpy/library/specs/ipv6.py index 39b7d04..e42e697 100644 --- a/src/bf_pktpy/library/specs/ipv6.py +++ b/src/bf_pktpy/library/specs/ipv6.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" IPv6 class """ +"""IPv6 class""" from bf_pktpy.library.specs.container import Container from bf_pktpy.library.specs.templates.ipv6 import IPv6 as IPv6Template diff --git a/src/bf_pktpy/library/specs/packet.py b/src/bf_pktpy/library/specs/packet.py index 91d75e1..2b07043 100644 --- a/src/bf_pktpy/library/specs/packet.py +++ b/src/bf_pktpy/library/specs/packet.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" Packet class """ +"""Packet class""" import six import warnings diff --git a/src/bf_pktpy/library/specs/pretty.py b/src/bf_pktpy/library/specs/pretty.py index 2543bbb..872fdcf 100644 --- a/src/bf_pktpy/library/specs/pretty.py +++ b/src/bf_pktpy/library/specs/pretty.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" Pretty module to transform code output """ +"""Pretty module to transform code output""" import json import re import six diff --git a/src/bf_pktpy/library/specs/tcp.py b/src/bf_pktpy/library/specs/tcp.py index 5fd34af..7ad573e 100644 --- a/src/bf_pktpy/library/specs/tcp.py +++ b/src/bf_pktpy/library/specs/tcp.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" TCP class """ +"""TCP class""" from bf_pktpy.library.specs.container import Container from bf_pktpy.library.specs.templates.tcp import TCP as TCPTemplate diff --git a/src/bf_pktpy/library/specs/templates/arp.py b/src/bf_pktpy/library/specs/templates/arp.py index 785c210..0dedaac 100644 --- a/src/bf_pktpy/library/specs/templates/arp.py +++ b/src/bf_pktpy/library/specs/templates/arp.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" ARP template """ +"""ARP template""" import ipaddress import six diff --git a/src/bf_pktpy/library/specs/templates/bfd.py b/src/bf_pktpy/library/specs/templates/bfd.py index 5a63909..6f7e07d 100644 --- a/src/bf_pktpy/library/specs/templates/bfd.py +++ b/src/bf_pktpy/library/specs/templates/bfd.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" BFD template """ +"""BFD template""" from bf_pktpy.library.specs.packet import Packet from bf_pktpy.library.fields import ( BitField, diff --git a/src/bf_pktpy/library/specs/templates/bootp.py b/src/bf_pktpy/library/specs/templates/bootp.py index 623b4d9..8175da6 100644 --- a/src/bf_pktpy/library/specs/templates/bootp.py +++ b/src/bf_pktpy/library/specs/templates/bootp.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" BOOTP template """ +"""BOOTP template""" from bf_pktpy.library.specs.base import Base diff --git a/src/bf_pktpy/library/specs/templates/control.py b/src/bf_pktpy/library/specs/templates/control.py index 0459ce3..7668e6a 100644 --- a/src/bf_pktpy/library/specs/templates/control.py +++ b/src/bf_pktpy/library/specs/templates/control.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" Stream control template """ +"""Stream control template""" from bf_pktpy.library.specs.base import Base diff --git a/src/bf_pktpy/library/specs/templates/cpu/dtel_report_hdr.py b/src/bf_pktpy/library/specs/templates/cpu/dtel_report_hdr.py index 5318c35..fcba95c 100644 --- a/src/bf_pktpy/library/specs/templates/cpu/dtel_report_hdr.py +++ b/src/bf_pktpy/library/specs/templates/cpu/dtel_report_hdr.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" DtelReportHdr template """ +"""DtelReportHdr template""" from bf_pktpy.library.specs.packet import Packet from bf_pktpy.library.fields import BitField, IntField diff --git a/src/bf_pktpy/library/specs/templates/cpu/dtel_report_v2_hdr.py b/src/bf_pktpy/library/specs/templates/cpu/dtel_report_v2_hdr.py index a5aeecd..5939e80 100644 --- a/src/bf_pktpy/library/specs/templates/cpu/dtel_report_v2_hdr.py +++ b/src/bf_pktpy/library/specs/templates/cpu/dtel_report_v2_hdr.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" DtelReportV2Hdr template """ +"""DtelReportV2Hdr template""" from bf_pktpy.library.specs.packet import Packet from bf_pktpy.library.fields import ( BitField, diff --git a/src/bf_pktpy/library/specs/templates/cpu/fabric_cpu_bfd_event_header.py b/src/bf_pktpy/library/specs/templates/cpu/fabric_cpu_bfd_event_header.py index b485885..a5b5211 100644 --- a/src/bf_pktpy/library/specs/templates/cpu/fabric_cpu_bfd_event_header.py +++ b/src/bf_pktpy/library/specs/templates/cpu/fabric_cpu_bfd_event_header.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" FabricCpuBfdEventHeader template """ +"""FabricCpuBfdEventHeader template""" from bf_pktpy.library.specs.packet import Packet from bf_pktpy.library.fields import ShortField diff --git a/src/bf_pktpy/library/specs/templates/cpu/fabric_cpu_header.py b/src/bf_pktpy/library/specs/templates/cpu/fabric_cpu_header.py index 37f1127..11ff37d 100644 --- a/src/bf_pktpy/library/specs/templates/cpu/fabric_cpu_header.py +++ b/src/bf_pktpy/library/specs/templates/cpu/fabric_cpu_header.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" FabricCpuHeader template """ +"""FabricCpuHeader template""" from bf_pktpy.library.specs.packet import Packet from bf_pktpy.library.fields import BitField, ShortField diff --git a/src/bf_pktpy/library/specs/templates/cpu/fabric_cpu_sflow_header.py b/src/bf_pktpy/library/specs/templates/cpu/fabric_cpu_sflow_header.py index c87bd8f..5268ea7 100644 --- a/src/bf_pktpy/library/specs/templates/cpu/fabric_cpu_sflow_header.py +++ b/src/bf_pktpy/library/specs/templates/cpu/fabric_cpu_sflow_header.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" FabricCpuSflowHeader template """ +"""FabricCpuSflowHeader template""" from bf_pktpy.library.specs.packet import Packet from bf_pktpy.library.fields import ShortField diff --git a/src/bf_pktpy/library/specs/templates/cpu/fabric_cpu_timestamp_header.py b/src/bf_pktpy/library/specs/templates/cpu/fabric_cpu_timestamp_header.py index 7d72dbe..e7ce2fd 100644 --- a/src/bf_pktpy/library/specs/templates/cpu/fabric_cpu_timestamp_header.py +++ b/src/bf_pktpy/library/specs/templates/cpu/fabric_cpu_timestamp_header.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" FabricCpuTimestampHeader template """ +"""FabricCpuTimestampHeader template""" from bf_pktpy.library.specs.packet import Packet from bf_pktpy.library.fields import ThreeBytesField diff --git a/src/bf_pktpy/library/specs/templates/cpu/fabric_header.py b/src/bf_pktpy/library/specs/templates/cpu/fabric_header.py index 95af7c6..fd2644d 100644 --- a/src/bf_pktpy/library/specs/templates/cpu/fabric_header.py +++ b/src/bf_pktpy/library/specs/templates/cpu/fabric_header.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" FabricHeader template """ +"""FabricHeader template""" from bf_pktpy.library.specs.packet import Packet from bf_pktpy.library.fields import BitField, ByteField diff --git a/src/bf_pktpy/library/specs/templates/cpu/fabric_multicast_header.py b/src/bf_pktpy/library/specs/templates/cpu/fabric_multicast_header.py index 9951f3a..ca5e8ee 100644 --- a/src/bf_pktpy/library/specs/templates/cpu/fabric_multicast_header.py +++ b/src/bf_pktpy/library/specs/templates/cpu/fabric_multicast_header.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" FabricMulticastHeader template """ +"""FabricMulticastHeader template""" from bf_pktpy.library.specs.packet import Packet from bf_pktpy.library.fields import BitField, ShortField diff --git a/src/bf_pktpy/library/specs/templates/cpu/fabric_payload_header.py b/src/bf_pktpy/library/specs/templates/cpu/fabric_payload_header.py index 2cedbce..cd5f269 100644 --- a/src/bf_pktpy/library/specs/templates/cpu/fabric_payload_header.py +++ b/src/bf_pktpy/library/specs/templates/cpu/fabric_payload_header.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" FabricPayloadHeader template """ +"""FabricPayloadHeader template""" from bf_pktpy.library.specs.packet import Packet from bf_pktpy.library.fields import ShortField diff --git a/src/bf_pktpy/library/specs/templates/cpu/fabric_unicast_header.py b/src/bf_pktpy/library/specs/templates/cpu/fabric_unicast_header.py index f909306..71d6eb3 100644 --- a/src/bf_pktpy/library/specs/templates/cpu/fabric_unicast_header.py +++ b/src/bf_pktpy/library/specs/templates/cpu/fabric_unicast_header.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" FabricUnicastHeader template """ +"""FabricUnicastHeader template""" from bf_pktpy.library.specs.packet import Packet from bf_pktpy.library.fields import BitField, ShortField diff --git a/src/bf_pktpy/library/specs/templates/cpu/mod_header.py b/src/bf_pktpy/library/specs/templates/cpu/mod_header.py index 1a64924..0648588 100644 --- a/src/bf_pktpy/library/specs/templates/cpu/mod_header.py +++ b/src/bf_pktpy/library/specs/templates/cpu/mod_header.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" ModHeader template """ +"""ModHeader template""" from bf_pktpy.library.specs.packet import Packet from bf_pktpy.library.fields import ByteField, ShortField, IntField diff --git a/src/bf_pktpy/library/specs/templates/cpu/postcard_header.py b/src/bf_pktpy/library/specs/templates/cpu/postcard_header.py index ef8c127..d65e63d 100644 --- a/src/bf_pktpy/library/specs/templates/cpu/postcard_header.py +++ b/src/bf_pktpy/library/specs/templates/cpu/postcard_header.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" PostcardHeader template """ +"""PostcardHeader template""" from bf_pktpy.library.specs.packet import Packet from bf_pktpy.library.fields import ByteField, ShortField, ThreeBytesField, IntField diff --git a/src/bf_pktpy/library/specs/templates/cpu/simple_l3_mirror_cpu_header.py b/src/bf_pktpy/library/specs/templates/cpu/simple_l3_mirror_cpu_header.py index 9833904..3d575ff 100644 --- a/src/bf_pktpy/library/specs/templates/cpu/simple_l3_mirror_cpu_header.py +++ b/src/bf_pktpy/library/specs/templates/cpu/simple_l3_mirror_cpu_header.py @@ -14,7 +14,7 @@ # implied warranties, other than those that are expressly stated in the License. ############################################################################### -""" SimpleL3SwitchCpuHeader template """ +"""SimpleL3SwitchCpuHeader template""" import six from bf_pktpy.library.specs.packet import Packet from bf_pktpy.library.fields import ShortEnumField, ShortField diff --git a/src/bf_pktpy/library/specs/templates/dhcp.py b/src/bf_pktpy/library/specs/templates/dhcp.py index 78f95a7..9e1d8dd 100644 --- a/src/bf_pktpy/library/specs/templates/dhcp.py +++ b/src/bf_pktpy/library/specs/templates/dhcp.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" DHCP template """ +"""DHCP template""" from bf_pktpy.library.specs.base import Base import ipaddress diff --git a/src/bf_pktpy/library/specs/templates/dot1ad.py b/src/bf_pktpy/library/specs/templates/dot1ad.py index 639abad..7d0b3d8 100644 --- a/src/bf_pktpy/library/specs/templates/dot1ad.py +++ b/src/bf_pktpy/library/specs/templates/dot1ad.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" Dot1AD template """ +"""Dot1AD template""" from bf_pktpy.library.specs.templates.dot1q import Dot1Q diff --git a/src/bf_pktpy/library/specs/templates/dot1q.py b/src/bf_pktpy/library/specs/templates/dot1q.py index e4e3ea9..b4204db 100644 --- a/src/bf_pktpy/library/specs/templates/dot1q.py +++ b/src/bf_pktpy/library/specs/templates/dot1q.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" Dot1Q template """ +"""Dot1Q template""" from bf_pktpy.library.fields import BitField, XShortEnumField from bf_pktpy.library.helpers.ether_types import ETYPES diff --git a/src/bf_pktpy/library/specs/templates/erspan/alternative/erspan.py b/src/bf_pktpy/library/specs/templates/erspan/alternative/erspan.py index 1010536..eb66eb3 100644 --- a/src/bf_pktpy/library/specs/templates/erspan/alternative/erspan.py +++ b/src/bf_pktpy/library/specs/templates/erspan/alternative/erspan.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" ERSPAN template """ +"""ERSPAN template""" from bf_pktpy.library.specs.base import Base from bf_pktpy.library.specs.validate import ToBeBitField, ToBeIntegerField diff --git a/src/bf_pktpy/library/specs/templates/erspan/alternative/erspan_iii.py b/src/bf_pktpy/library/specs/templates/erspan/alternative/erspan_iii.py index 182cf46..74a465e 100644 --- a/src/bf_pktpy/library/specs/templates/erspan/alternative/erspan_iii.py +++ b/src/bf_pktpy/library/specs/templates/erspan/alternative/erspan_iii.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" ERSPAN_III template """ +"""ERSPAN_III template""" from bf_pktpy.library.specs.base import Base from bf_pktpy.library.specs.validate import ToBeBitField, ToBeIntegerField diff --git a/src/bf_pktpy/library/specs/templates/erspan/alternative/platform_specific.py b/src/bf_pktpy/library/specs/templates/erspan/alternative/platform_specific.py index fabbcd7..3cc1494 100644 --- a/src/bf_pktpy/library/specs/templates/erspan/alternative/platform_specific.py +++ b/src/bf_pktpy/library/specs/templates/erspan/alternative/platform_specific.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" PlatformSpecific template """ +"""PlatformSpecific template""" from bf_pktpy.library.specs.base import Base from bf_pktpy.library.specs.validate import ToBeBitField, ToBeIntegerField diff --git a/src/bf_pktpy/library/specs/templates/erspan/erspan.py b/src/bf_pktpy/library/specs/templates/erspan/erspan.py index e30aded..2d1d92c 100644 --- a/src/bf_pktpy/library/specs/templates/erspan/erspan.py +++ b/src/bf_pktpy/library/specs/templates/erspan/erspan.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" ERSPAN template """ +"""ERSPAN template""" from bf_pktpy.library.specs.templates.erspan.erspan_ii import ERSPAN_II diff --git a/src/bf_pktpy/library/specs/templates/erspan/erspan_ii.py b/src/bf_pktpy/library/specs/templates/erspan/erspan_ii.py index 7f0fcbe..a0a92f4 100644 --- a/src/bf_pktpy/library/specs/templates/erspan/erspan_ii.py +++ b/src/bf_pktpy/library/specs/templates/erspan/erspan_ii.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" ERSPAN_II template """ +"""ERSPAN_II template""" from bf_pktpy.library.specs.packet import Packet from bf_pktpy.library.fields import BitField diff --git a/src/bf_pktpy/library/specs/templates/erspan/erspan_iii.py b/src/bf_pktpy/library/specs/templates/erspan/erspan_iii.py index 89be006..6c9ee4e 100644 --- a/src/bf_pktpy/library/specs/templates/erspan/erspan_iii.py +++ b/src/bf_pktpy/library/specs/templates/erspan/erspan_iii.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" ERSPAN_III template """ +"""ERSPAN_III template""" from bf_pktpy.library.specs.packet import Packet from bf_pktpy.library.fields import BitField, XIntField, XShortField, BitEnumField diff --git a/src/bf_pktpy/library/specs/templates/erspan/erspan_platform_specific.py b/src/bf_pktpy/library/specs/templates/erspan/erspan_platform_specific.py index 35aac31..725afc4 100644 --- a/src/bf_pktpy/library/specs/templates/erspan/erspan_platform_specific.py +++ b/src/bf_pktpy/library/specs/templates/erspan/erspan_platform_specific.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" ERSPAN_OLD Platform Specific template """ +"""ERSPAN_OLD Platform Specific template""" from bf_pktpy.library.specs.packet import Packet from bf_pktpy.library.fields import BitField, XIntField diff --git a/src/bf_pktpy/library/specs/templates/ethernet.py b/src/bf_pktpy/library/specs/templates/ethernet.py index 3a3ab19..98dbb91 100644 --- a/src/bf_pktpy/library/specs/templates/ethernet.py +++ b/src/bf_pktpy/library/specs/templates/ethernet.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" Ether template """ +"""Ether template""" from bf_pktpy.library.helpers.ether_types import ETYPES from bf_pktpy.library.specs.packet import Packet from bf_pktpy.library.fields import SourceMACField, DestMACField, XShortEnumField diff --git a/src/bf_pktpy/library/specs/templates/frame.py b/src/bf_pktpy/library/specs/templates/frame.py index 2cb6d29..95f073a 100644 --- a/src/bf_pktpy/library/specs/templates/frame.py +++ b/src/bf_pktpy/library/specs/templates/frame.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" Frame template """ +"""Frame template""" from bf_pktpy.library.specs.base import Base diff --git a/src/bf_pktpy/library/specs/templates/gre.py b/src/bf_pktpy/library/specs/templates/gre.py index d674435..39d44c1 100644 --- a/src/bf_pktpy/library/specs/templates/gre.py +++ b/src/bf_pktpy/library/specs/templates/gre.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" GRE template """ +"""GRE template""" from bf_pktpy.library.helpers.bin import to_bin from bf_pktpy.library.helpers.chksum import checksum from bf_pktpy.library.helpers.ether_types import ETYPES diff --git a/src/bf_pktpy/library/specs/templates/icmp.py b/src/bf_pktpy/library/specs/templates/icmp.py index 41f685f..e8ad7aa 100644 --- a/src/bf_pktpy/library/specs/templates/icmp.py +++ b/src/bf_pktpy/library/specs/templates/icmp.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" ICMP template """ +"""ICMP template""" from bf_pktpy.library.specs.base import Base diff --git a/src/bf_pktpy/library/specs/templates/icmpv6_unknown.py b/src/bf_pktpy/library/specs/templates/icmpv6_unknown.py index db2eee7..64e841c 100644 --- a/src/bf_pktpy/library/specs/templates/icmpv6_unknown.py +++ b/src/bf_pktpy/library/specs/templates/icmpv6_unknown.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" ICMPv6Unknown template """ +"""ICMPv6Unknown template""" from bf_pktpy.library.specs.base import Base diff --git a/src/bf_pktpy/library/specs/templates/igmp.py b/src/bf_pktpy/library/specs/templates/igmp.py index 8f3737c..7e818f0 100644 --- a/src/bf_pktpy/library/specs/templates/igmp.py +++ b/src/bf_pktpy/library/specs/templates/igmp.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" IGMP template """ +"""IGMP template""" import ipaddress import six diff --git a/src/bf_pktpy/library/specs/templates/ipoption.py b/src/bf_pktpy/library/specs/templates/ipoption.py index f0a6e39..8d4f592 100644 --- a/src/bf_pktpy/library/specs/templates/ipoption.py +++ b/src/bf_pktpy/library/specs/templates/ipoption.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" IPOption templates """ +"""IPOption templates""" import six from bf_pktpy.library.specs.packet import Packet diff --git a/src/bf_pktpy/library/specs/templates/ipv4.py b/src/bf_pktpy/library/specs/templates/ipv4.py index 40b9038..4450381 100644 --- a/src/bf_pktpy/library/specs/templates/ipv4.py +++ b/src/bf_pktpy/library/specs/templates/ipv4.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" IP template """ +"""IP template""" import six from bf_pktpy.library.helpers.bin import to_bin diff --git a/src/bf_pktpy/library/specs/templates/ipv6.py b/src/bf_pktpy/library/specs/templates/ipv6.py index e7b663f..3a4ecb3 100644 --- a/src/bf_pktpy/library/specs/templates/ipv6.py +++ b/src/bf_pktpy/library/specs/templates/ipv6.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" IPv6 template """ +"""IPv6 template""" import ipaddress import six diff --git a/src/bf_pktpy/library/specs/templates/ipv6_ext_hdr_routing.py b/src/bf_pktpy/library/specs/templates/ipv6_ext_hdr_routing.py index 0ceb5bb..25fbab6 100644 --- a/src/bf_pktpy/library/specs/templates/ipv6_ext_hdr_routing.py +++ b/src/bf_pktpy/library/specs/templates/ipv6_ext_hdr_routing.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" IPv6ExtHdrRouting template """ +"""IPv6ExtHdrRouting template""" from bf_pktpy.library.specs.base import Base diff --git a/src/bf_pktpy/library/specs/templates/mpls.py b/src/bf_pktpy/library/specs/templates/mpls.py index 4906d0d..11526ea 100644 --- a/src/bf_pktpy/library/specs/templates/mpls.py +++ b/src/bf_pktpy/library/specs/templates/mpls.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" MPLS template """ +"""MPLS template""" from bf_pktpy.library.specs.packet import Packet from bf_pktpy.library.fields import BitField, ByteField diff --git a/src/bf_pktpy/library/specs/templates/payload.py b/src/bf_pktpy/library/specs/templates/payload.py index d2eb270..3447f98 100644 --- a/src/bf_pktpy/library/specs/templates/payload.py +++ b/src/bf_pktpy/library/specs/templates/payload.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" Payload template """ +"""Payload template""" from bf_pktpy.library.specs.base import Base diff --git a/src/bf_pktpy/library/specs/templates/sfc/sfc_roce.py b/src/bf_pktpy/library/specs/templates/sfc/sfc_roce.py index 965281c..d60d83a 100644 --- a/src/bf_pktpy/library/specs/templates/sfc/sfc_roce.py +++ b/src/bf_pktpy/library/specs/templates/sfc/sfc_roce.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" RoCE headers """ +"""RoCE headers""" from enum import Enum diff --git a/src/bf_pktpy/library/specs/templates/tcp.py b/src/bf_pktpy/library/specs/templates/tcp.py index f7c5d2e..1fa59e9 100644 --- a/src/bf_pktpy/library/specs/templates/tcp.py +++ b/src/bf_pktpy/library/specs/templates/tcp.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" TCP template """ +"""TCP template""" import math from bf_pktpy.library.specs.base import Base diff --git a/src/bf_pktpy/library/specs/templates/tcpoption.py b/src/bf_pktpy/library/specs/templates/tcpoption.py index 0100424..717b54f 100644 --- a/src/bf_pktpy/library/specs/templates/tcpoption.py +++ b/src/bf_pktpy/library/specs/templates/tcpoption.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" TCPOption template """ +"""TCPOption template""" from bf_pktpy.library.specs.base import Base diff --git a/src/bf_pktpy/library/specs/templates/udp.py b/src/bf_pktpy/library/specs/templates/udp.py index 4e109a4..f7ed088 100644 --- a/src/bf_pktpy/library/specs/templates/udp.py +++ b/src/bf_pktpy/library/specs/templates/udp.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" UDP template """ +"""UDP template""" from bf_pktpy.library.helpers.bin import to_bin from bf_pktpy.library.helpers.chksum import checksum from bf_pktpy.library.specs.packet import Packet diff --git a/src/bf_pktpy/library/specs/templates/vxlan.py b/src/bf_pktpy/library/specs/templates/vxlan.py index 337ce73..eefd747 100644 --- a/src/bf_pktpy/library/specs/templates/vxlan.py +++ b/src/bf_pktpy/library/specs/templates/vxlan.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" VXLAN template """ +"""VXLAN template""" from bf_pktpy.library.specs.base import Base diff --git a/src/bf_pktpy/library/specs/udp.py b/src/bf_pktpy/library/specs/udp.py index 457b7f9..65f4b7c 100644 --- a/src/bf_pktpy/library/specs/udp.py +++ b/src/bf_pktpy/library/specs/udp.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" UDP class """ +"""UDP class""" from bf_pktpy.library.specs.container import Container from bf_pktpy.library.specs.templates.udp import UDP as UDPTemplate diff --git a/src/bf_pktpy/library/specs/validate.py b/src/bf_pktpy/library/specs/validate.py index beb0a8e..73c36ed 100644 --- a/src/bf_pktpy/library/specs/validate.py +++ b/src/bf_pktpy/library/specs/validate.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" Enforce module to validate instance variables """ +"""Enforce module to validate instance variables""" import ipaddress import re import six diff --git a/src/bf_pktpy/library/utils/answer.py b/src/bf_pktpy/library/utils/answer.py index 2adddae..8aef964 100644 --- a/src/bf_pktpy/library/utils/answer.py +++ b/src/bf_pktpy/library/utils/answer.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" Answer module """ +"""Answer module""" # ============================================================================= diff --git a/src/bf_pktpy/library/utils/bridge_and_sniff.py b/src/bf_pktpy/library/utils/bridge_and_sniff.py index 97e2594..5b94c40 100644 --- a/src/bf_pktpy/library/utils/bridge_and_sniff.py +++ b/src/bf_pktpy/library/utils/bridge_and_sniff.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" BridgeSniff module """ +"""BridgeSniff module""" import socket import time import threading diff --git a/src/bf_pktpy/library/utils/decoder.py b/src/bf_pktpy/library/utils/decoder.py index bc22c30..3be9c58 100644 --- a/src/bf_pktpy/library/utils/decoder.py +++ b/src/bf_pktpy/library/utils/decoder.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" Decoder module """ +"""Decoder module""" from bf_pktpy.library.specs.templates.ethernet import Ether from bf_pktpy.library.specs.templates.ipv4 import IP from bf_pktpy.library.specs.templates.icmp import ICMP diff --git a/src/bf_pktpy/library/utils/interface.py b/src/bf_pktpy/library/utils/interface.py index a41b706..a7e3fd5 100644 --- a/src/bf_pktpy/library/utils/interface.py +++ b/src/bf_pktpy/library/utils/interface.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" Interface module """ +"""Interface module""" import socket import subprocess from collections import namedtuple diff --git a/src/bf_pktpy/library/utils/listener.py b/src/bf_pktpy/library/utils/listener.py index e8d2d7a..92ee3d8 100644 --- a/src/bf_pktpy/library/utils/listener.py +++ b/src/bf_pktpy/library/utils/listener.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" Listener module """ +"""Listener module""" import binascii import socket import threading diff --git a/src/bf_pktpy/library/utils/ls.py b/src/bf_pktpy/library/utils/ls.py index 8c3e87f..ce64615 100644 --- a/src/bf_pktpy/library/utils/ls.py +++ b/src/bf_pktpy/library/utils/ls.py @@ -120,9 +120,11 @@ def _ls_packet(layer): type(field).__name__, field.size(layer) if callable(field.size) else field.size, getattr(layer, field.name), - field.default_value(layer) - if callable(field.default_value) - else field.default_value, + ( + field.default_value(layer) + if callable(field.default_value) + else field.default_value + ), ) for field in layer.fields_desc ] diff --git a/src/bf_pktpy/library/utils/sniff.py b/src/bf_pktpy/library/utils/sniff.py index c4d179e..d355fc9 100644 --- a/src/bf_pktpy/library/utils/sniff.py +++ b/src/bf_pktpy/library/utils/sniff.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" Sniffer module """ +"""Sniffer module""" import binascii from six.moves import queue import socket diff --git a/src/bf_pktpy/library/utils/stream.py b/src/bf_pktpy/library/utils/stream.py index 60de1b6..6526776 100644 --- a/src/bf_pktpy/library/utils/stream.py +++ b/src/bf_pktpy/library/utils/stream.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" Stream module """ +"""Stream module""" import time from bf_pktpy.library.utils.interface import Interface diff --git a/src/bf_pktpy/library/utils/tool.py b/src/bf_pktpy/library/utils/tool.py index d731f79..ce42e1d 100644 --- a/src/bf_pktpy/library/utils/tool.py +++ b/src/bf_pktpy/library/utils/tool.py @@ -14,7 +14,7 @@ # limitations under the License. ############################################################################### -""" bf_pktpy python library """ +"""bf_pktpy python library""" import socket From 4953210fa67c974d5b5d7445b527e20dc1a624eb Mon Sep 17 00:00:00 2001 From: Andy Fingerhut Date: Fri, 14 Mar 2025 15:34:46 +0000 Subject: [PATCH 4/5] Make more function names usable via ptf when common in bf-pktpy and scapy Signed-off-by: Andy Fingerhut --- src/bf_pktpy/ptf/packet_pktpy.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/bf_pktpy/ptf/packet_pktpy.py b/src/bf_pktpy/ptf/packet_pktpy.py index e8e00b0..b29ac74 100644 --- a/src/bf_pktpy/ptf/packet_pktpy.py +++ b/src/bf_pktpy/ptf/packet_pktpy.py @@ -19,6 +19,7 @@ see PTF documentation (section "Pluggable packet manipulation module"). """ import bf_pktpy.packets +import bf_pktpy.commands from bf_pktpy.all import hexdump as bf_pktpy_hexdump, ls as bf_pktpy_ls from ptf import config @@ -119,3 +120,20 @@ def get_erspan_alternative(): # bf_pktpy implementation of hexdump hexdump = bf_pktpy_hexdump ls = bf_pktpy_ls + +# The names below are assigned here so that, like the other names +# above, they can be used by importers of the ptf.packet module as if +# they were defined inside of ptf.packet, and they are commonly +# available as ptf.packet. regardless whether you use scapy or +# bf-pktpy as the packet manipulation module. + +send = bf_pktpy.commands.send +sendp = bf_pktpy.commands.sendp +sr = bf_pktpy.commands.sr +sr1 = bf_pktpy.commands.sr1 +srp = bf_pktpy.commands.srp +srp1 = bf_pktpy.commands.srp1 +srloop = bf_pktpy.commands.srloop +srploop = bf_pktpy.commands.srploop +sniff = bf_pktpy.commands.sniff +bridge_and_sniff = bf_pktpy.commands.bridge_and_sniff From 86f40951e78d48c4854db2099902b94099195ab2 Mon Sep 17 00:00:00 2001 From: Andy Fingerhut Date: Fri, 14 Mar 2025 19:05:28 +0000 Subject: [PATCH 5/5] Update README for bf_pktpy being included as part of the ptf package Signed-off-by: Andy Fingerhut --- README.md | 48 ++++++++++-------------------------------------- 1 file changed, 10 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 67d02a1..4f49d69 100644 --- a/README.md +++ b/README.md @@ -42,9 +42,12 @@ More information about Black, you find at The following software is required to run PTF: * Python 3.x - * Scapy 2.5.0 (unless you provide another packet manipulation module) - * pypcap (optional - VLAN tests will fail without this) - * tcpdump (optional - Scapy will complain if it's missing) + +The following packages are optional for running PTF: + + * Scapy 2.5.0 (you may also use the included `bf_pktpy` module instead) + * pypcap (VLAN tests will fail without this) + * tcpdump (Scapy will complain if it's missing) Root/sudo privilege is required on the host, in order to run `ptf`. @@ -74,11 +77,7 @@ apt-get install tcpdump ### Using `bf_pktpy` as an alternate packet manipulation module -The Python module `bf_pktpy` is included as part of the -`open-p4studio` repository: - -+ https://github.com/p4lang/open-p4studio/tree/main/pkgsrc/ptf-modules/bf-pktpy - +The Python module `bf_pktpy` is included as part of the ptf package. It was developed as an alternative to `scapy`. The tradeoffs of using `bf_pktpy` vs. `scapy` are: @@ -89,34 +88,7 @@ It was developed as an alternative to `scapy`. The tradeoffs of using under a different license. + `bf_pktpy` implements only a small subset of the functionality of `scapy`, but it does include support for very commonly-used packet - headers. It is released under an Apache 2.0 license (see - https://github.com/p4lang/open-p4studio/blob/main/pkgsrc/ptf-modules/bf-pktpy/LICENSE). - -The package `bf_pktpy` is not currently available from PyPI. To -install `bf_pktpy` from source code, choose one of these methods: - -```bash -pip install "bf-pktpy@git+https://github.com/p4lang/open-p4studio#subdirectory=pkgsrc/ptf-utils/bf-pktpy" -``` - -```bash -git clone https://github.com/p4lang/open-p4studio -cd open-p4studio/pkgsrc/ptf-utils/bf-pktpy -pip install . -``` - -To make effective use of `bf_pktpy` you must also install these -additional Python packages. All are released under an MIT or -BSD-3-Clause license, which are compatible for releasing with -`bf_pktpy`, or for importing in a project with most licenses, -including `Apache-2.0`, `BSD-3-Clause`, `GPL-2.0-only`, or many -others. - -```bash -pip install six getmac scapy_helper psutil -sudo apt-get install python3-dev -pip install netifaces -``` + headers. It is released under an Apache 2.0 license. If you want to use `bf_pktpy` when running the command `ptf` from the command line, provide the `-pmm` option as shown below. @@ -274,9 +246,9 @@ timeout takes precedence over the global timeout passed on the command line. ## Pluggable packet manipulation module By default, `ptf` uses `Scapy` as the packet manipulation module, but it can -also operate on a different one. +also operate on a different one, e.g. the included `bf_pktpy` module. -Such module **must define/implement the same symbols**, as defined in `Scapy` +Such a module **must define/implement the same symbols**, as defined in `Scapy` implementation of packet. Most of them are just names of most common frame headers (Ether, IP, TCP, UDP, ...).