From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mails.dpdk.org (mails.dpdk.org [217.70.189.124]) by inbox.dpdk.org (Postfix) with ESMTP id 1E75043357; Fri, 17 Nov 2023 19:06:07 +0100 (CET) Received: from mails.dpdk.org (localhost [127.0.0.1]) by mails.dpdk.org (Postfix) with ESMTP id A0829402EA; Fri, 17 Nov 2023 19:06:06 +0100 (CET) Received: from mail-pg1-f173.google.com (mail-pg1-f173.google.com [209.85.215.173]) by mails.dpdk.org (Postfix) with ESMTP id 66E4740285 for ; Fri, 17 Nov 2023 19:06:05 +0100 (CET) Received: by mail-pg1-f173.google.com with SMTP id 41be03b00d2f7-5bd6ac9833fso1621781a12.0 for ; Fri, 17 Nov 2023 10:06:05 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=iol.unh.edu; s=unh-iol; t=1700244364; x=1700849164; darn=dpdk.org; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:from:to:cc:subject:date:message-id:reply-to; bh=r2PQ0Cw2P1OoTblGWd3Knhdkk24XHJ3K6vzq58cO+VY=; b=LiwSty53xR+p8+JyaUWim4bt32IFxKfaftDaQs1ArxOZ1VGt3U9zLPgIStnBHdS4rh wPsxDyHMKvNc/8szEo0oiijJLOhpJRF2sAKhksxzzwUFKJMRN/1F4Ho2jWK6yjtS0S5t PVvX7rekR8Xb8oa3LwEv8MahqDGRzcNCbpUyY= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1700244364; x=1700849164; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=r2PQ0Cw2P1OoTblGWd3Knhdkk24XHJ3K6vzq58cO+VY=; b=KBerw2qw/bV/0mQfgYb+LYoIWLjyE0HNiCW0cVRel18Px6zT1EKQhl+UG6z61+vEfP d3UEybLMQxfdzZPTUpyUt54k17jz/XCFaVH7GD01zI3fe20fZmnL5DHkZFQvtXL0wBvH 6vsXy1nN0XJQnJCEK2fSIJcop+iNC6P2kK1ov6fqVxnaYr/PWdNbsX4VEGP+gt6YyBUU cuCalQf2i1yPPlmyTlDXqYqJ+yeFHbjHK/Ei3j8NkhZu7mVSPabscs0+0zadbNsZI8jn 1ia44R1HnXxke9Y+kQ/VFhuKGarJ2tXegwr/SXk2N340DFfMM6G0Dr2Rr+QDJZJWxVcd 8E9g== X-Gm-Message-State: AOJu0Yz8C5/k3IruNVkkx96YM7pg/YQq+gLtvR+UK8iyV9CMsMBJNV9N FudgBG2Q5FRHMyf92fRVp5qbAUvFITzdAEPtrOJgEQ== X-Google-Smtp-Source: AGHT+IEEAR4UW/RkV4LH5UNK3tQdQR50tZiDFNAraVev3CZSQVL0B89kNVUZlec6U0be022KP9o798MZmxTNEGrUTaU= X-Received: by 2002:a17:90b:4d89:b0:27d:237b:558b with SMTP id oj9-20020a17090b4d8900b0027d237b558bmr296220pjb.5.1700244364398; Fri, 17 Nov 2023 10:06:04 -0800 (PST) MIME-Version: 1.0 References: <20231113202833.12900-1-jspewock@iol.unh.edu> <20231113202833.12900-6-jspewock@iol.unh.edu> In-Reply-To: From: Jeremy Spewock Date: Fri, 17 Nov 2023 13:05:53 -0500 Message-ID: Subject: Re: [PATCH v3 5/7] dts: add optional packet filtering to scapy sniffer To: =?UTF-8?Q?Juraj_Linke=C5=A1?= Cc: Honnappa.Nagarahalli@arm.com, thomas@monjalon.net, wathsala.vithanage@arm.com, probb@iol.unh.edu, paul.szczepanek@arm.com, yoan.picchi@foss.arm.com, ferruh.yigit@amd.com, andrew.rybchenko@oktetlabs.ru, dev@dpdk.org Content-Type: multipart/alternative; boundary="0000000000005f247b060a5cfdd6" X-BeenThere: dev@dpdk.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: DPDK patches and discussions List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dev-bounces@dpdk.org --0000000000005f247b060a5cfdd6 Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable On Thu, Nov 16, 2023 at 1:34=E2=80=AFPM Juraj Linke=C5=A1 wrote: > As I'm thinking about the filtering, it's not a trivial manner. For > now, I'd like to pass a class instead of multiple parameters, as that > will be easier to extend if we need to add to the filter. The class > could be a dataclass holding the various booleans. Then the capturing > traffic generators would need to implement a method that would > translate the dataclass into a TG-specific filters. I'm not sure we > want to introduce an abstract method for this, as the return value may > differ for different TGs - this needs more thought. > I agree that making it a dataclass and having a method within Scapy for interpreting it for now would work. You're right there there are some other considerations to be had like if the return types could be different. I would think that for the most part a string is what would be used, but it could be different depending on what traffic generator you use. I know that tcpdump supports BPFs as they are shown here, so if we went down the route of using that for collecting packets with other traffic generators we would be fine on that front, but that might need more discussion as well when we add those traffic generators. If we needed this to be more generic as well, it could be possible to assign each traffic generator a filter type and then we could make subclasses of the dataclass for each filter type. This way, in the filter subclasses, they could specify what each of the parameters meant and how to create the filter. This would always return the right data type for the filter and allow us to implement methods for some of the more generic filters (like the one I use here, a BPF) rather than implementing them on the traffic generator that uses the filter. Then we could just use a TypeVar to generically create the filters based on what the capturing traffic generator class says it would need. I think another way we could go about this problem however is just not filtering out packets that are generally just noise like these and instead just check the packets we receive when capturing and only return ones that are relevant (like ones that have the same layers as the packet we sent for example). However, I think because of only having one traffic generator it would be fine to just create a method i nthe scapy capturing traffic generator that knows how to create BPF filters and I can do that for the next version if that is preferred. > > On Mon, Nov 13, 2023 at 9:28=E2=80=AFPM wrote: > > > > From: Jeremy Spewock > > > > Added the options to filter out LLDP and ARP packets when > > sniffing for packets with scapy. This was done using BPF filters to > > ensure that the noise these packets provide does not interfere with tes= t > > cases. > > > > Signed-off-by: Jeremy Spewock > > --- > > dts/framework/test_suite.py | 13 +++++++++++-- > > .../testbed_model/capturing_traffic_generator.py | 12 +++++++++++- > > dts/framework/testbed_model/scapy.py | 11 ++++++++++- > > dts/framework/testbed_model/tg_node.py | 4 +++- > > 4 files changed, 35 insertions(+), 5 deletions(-) > > > > diff --git a/dts/framework/test_suite.py b/dts/framework/test_suite.py > > index 3b890c0451..3222f172f3 100644 > > --- a/dts/framework/test_suite.py > > +++ b/dts/framework/test_suite.py > > @@ -152,7 +152,11 @@ def _configure_ipv4_forwarding(self, enable: bool) > -> None: > > self.sut_node.configure_ipv4_forwarding(enable) > > > > def send_packet_and_capture( > > - self, packet: Packet, duration: float =3D 1 > > + self, > > + packet: Packet, > > + duration: float =3D 1, > > + no_lldp: bool =3D True, > > + no_arp: bool =3D True, > > The parameters of this method are not documented in the docstring, but > we should add these new parameters to the docstring. The same goes for > the other methods (and other parameters of other methods) if they're > not overriding an abstract method. > > The default should be False if these are meant to be optional, but I > like these defaulting to True, so I'd change the wording from optional > to configurable. > That is a good catch, I didn't think about the wording that way but that makes a lot of sense. I will make this change. > > > ) -> list[Packet]: > > """ > > Send a packet through the appropriate interface and > > @@ -162,7 +166,12 @@ def send_packet_and_capture( > > """ > > packet =3D self._adjust_addresses(packet) > > return self.tg_node.send_packet_and_capture( > > - packet, self._tg_port_egress, self._tg_port_ingress, > duration > > + packet, > > + self._tg_port_egress, > > + self._tg_port_ingress, > > + duration, > > + no_lldp, > > + no_arp, > > ) > > > > def get_expected_packet(self, packet: Packet) -> Packet: > > diff --git a/dts/framework/testbed_model/capturing_traffic_generator.py > b/dts/framework/testbed_model/capturing_traffic_generator.py > > index ab98987f8e..0a0d0f7d0d 100644 > > --- a/dts/framework/testbed_model/capturing_traffic_generator.py > > +++ b/dts/framework/testbed_model/capturing_traffic_generator.py > > @@ -52,6 +52,8 @@ def send_packet_and_capture( > > send_port: Port, > > receive_port: Port, > > duration: float, > > + no_lldp: bool, > > + no_arp: bool, > > capture_name: str =3D _get_default_capture_name(), > > ) -> list[Packet]: > > """Send a packet, return received traffic. > > @@ -71,7 +73,7 @@ def send_packet_and_capture( > > A list of received packets. May be empty if no packets ar= e > captured. > > """ > > return self.send_packets_and_capture( > > - [packet], send_port, receive_port, duration, capture_name > > + [packet], send_port, receive_port, duration, no_lldp, > no_arp, capture_name > > ) > > > > def send_packets_and_capture( > > @@ -80,6 +82,8 @@ def send_packets_and_capture( > > send_port: Port, > > receive_port: Port, > > duration: float, > > + no_lldp: bool, > > + no_arp: bool, > > capture_name: str =3D _get_default_capture_name(), > > ) -> list[Packet]: > > """Send packets, return received traffic. > > @@ -93,6 +97,8 @@ def send_packets_and_capture( > > send_port: The egress port on the TG node. > > receive_port: The ingress port in the TG node. > > duration: Capture traffic for this amount of time after > sending the packets. > > + no_lldp: Option to disable capturing LLDP packets > > + no_arp: Option to disable capturing ARP packets > > capture_name: The name of the .pcap file where to store th= e > capture. > > > > Returns: > > @@ -108,6 +114,8 @@ def send_packets_and_capture( > > send_port, > > receive_port, > > duration, > > + no_lldp, > > + no_arp, > > ) > > > > self._logger.debug( > > @@ -123,6 +131,8 @@ def _send_packets_and_capture( > > send_port: Port, > > receive_port: Port, > > duration: float, > > + no_lldp: bool, > > + no_arp: bool, > > ) -> list[Packet]: > > """ > > The extended classes must implement this method which > > diff --git a/dts/framework/testbed_model/scapy.py > b/dts/framework/testbed_model/scapy.py > > index af0d4dbb25..58f01af21a 100644 > > --- a/dts/framework/testbed_model/scapy.py > > +++ b/dts/framework/testbed_model/scapy.py > > @@ -69,6 +69,7 @@ def scapy_send_packets_and_capture( > > send_iface: str, > > recv_iface: str, > > duration: float, > > + sniff_filter: str, > > ) -> list[bytes]: > > """RPC function to send and capture packets. > > > > @@ -90,6 +91,7 @@ def scapy_send_packets_and_capture( > > iface=3Drecv_iface, > > store=3DTrue, > > started_callback=3Dlambda *args: scapy.all.sendp(scapy_packets= , > iface=3Dsend_iface), > > + filter=3Dsniff_filter, > > ) > > sniffer.start() > > time.sleep(duration) > > @@ -264,10 +266,16 @@ def _send_packets_and_capture( > > send_port: Port, > > receive_port: Port, > > duration: float, > > + no_lldp: bool, > > + no_arp: bool, > > capture_name: str =3D _get_default_capture_name(), > > ) -> list[Packet]: > > binary_packets =3D [packet.build() for packet in packets] > > - > > + sniff_filter =3D [] > > + if no_lldp: > > + sniff_filter.append("ether[12:2] !=3D 0x88cc") > > + if no_arp: > > + sniff_filter.append("ether[12:2] !=3D 0x0806") > > xmlrpc_packets: list[ > > xmlrpc.client.Binary > > ] =3D self.rpc_server_proxy.scapy_send_packets_and_capture( > > @@ -275,6 +283,7 @@ def _send_packets_and_capture( > > send_port.logical_name, > > receive_port.logical_name, > > duration, > > + " && ".join(sniff_filter), > > ) # type: ignore[assignment] > > > > scapy_packets =3D [Ether(packet.data) for packet in > xmlrpc_packets] > > diff --git a/dts/framework/testbed_model/tg_node.py > b/dts/framework/testbed_model/tg_node.py > > index 27025cfa31..98e55b7831 100644 > > --- a/dts/framework/testbed_model/tg_node.py > > +++ b/dts/framework/testbed_model/tg_node.py > > @@ -56,6 +56,8 @@ def send_packet_and_capture( > > send_port: Port, > > receive_port: Port, > > duration: float =3D 1, > > + no_lldp: bool =3D True, > > + no_arp: bool =3D True, > > ) -> list[Packet]: > > """Send a packet, return received traffic. > > > > @@ -73,7 +75,7 @@ def send_packet_and_capture( > > A list of received packets. May be empty if no packets ar= e > captured. > > """ > > return self.traffic_generator.send_packet_and_capture( > > - packet, send_port, receive_port, duration > > + packet, send_port, receive_port, duration, no_lldp, no_arp > > ) > > > > def close(self) -> None: > > -- > > 2.42.0 > > > --0000000000005f247b060a5cfdd6 Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable


<= div dir=3D"ltr" class=3D"gmail_attr">On Thu, Nov 16, 2023 at 1:34=E2=80=AFP= M Juraj Linke=C5=A1 <juraj.linkes@pantheon.tech> wrote:
As I'm thinking about the= filtering, it's not a trivial manner. For
now, I'd like to pass a class instead of multiple parameters, as that will be easier to extend if we need to add to the filter. The class
could be a dataclass holding the various booleans. Then the capturing
traffic generators would need to implement a method that would
translate the dataclass into a TG-specific filters. I'm not sure we
want to introduce an abstract method for this, as the return value may
differ for different TGs - this needs more thought.
I agree that making it a dataclass and having a method within Scapy f= or interpreting it for now would work. You're right there there are som= e other considerations to be had like if the return types could be differen= t. I would think that for the most part a string is what would be used, but= it could be different depending on what traffic generator you use. I know = that tcpdump supports BPFs as they are shown here, so if we went down the r= oute of using that for collecting packets with other traffic generators we = would be fine on that front, but that might need more discussion as well wh= en we add those traffic generators.

If we needed this to be more generic a= s well, it could be possible to assign each traffic generator a filter type= and then we could make subclasses of the dataclass for each filter type. T= his way, in the filter subclasses, they could specify what each of the para= meters meant and how to create the filter. This would always return the rig= ht data type for the filter and allow us to implement methods for some of t= he more generic filters (like the one I use here, a BPF) rather than implem= enting them on the traffic generator that uses the filter. Then we could ju= st use a TypeVar to generically create the filters based on what the captur= ing traffic generator class says it would need.

I think another way we cou= ld go about this problem however is just not filtering out packets that are= generally just noise like these and instead just check the packets we rece= ive when capturing and only return ones that are relevant (like ones that h= ave the same layers as the packet we sent for example). However, I think be= cause of only having one traffic generator it would be fine to just create = a method i nthe scapy capturing traffic generator that knows how to create = BPF filters and I can do that for the next version if that is preferred.
=C2=A0

On Mon, Nov 13, 2023 at 9:28=E2=80=AFPM <jspewock@iol.unh.edu> wrote:
>
> From: Jeremy Spewock <jspewock@iol.unh.edu>
>
> Added the options to filter out LLDP and ARP packets when
> sniffing for packets with scapy. This was done using BPF filters to > ensure that the noise these packets provide does not interfere with te= st
> cases.
>
> Signed-off-by: Jeremy Spewock <jspewock@iol.unh.edu>
> ---
>=C2=A0 dts/framework/test_suite.py=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0| 13 +++++++++++-- >=C2=A0 .../testbed_model/capturing_traffic_generator.py=C2=A0 =C2=A0 | = 12 +++++++++++-
>=C2=A0 dts/framework/testbed_model/scapy.py=C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A0 | 11 ++++++++++-
>=C2=A0 dts/framework/testbed_model/tg_node.py=C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 |=C2=A0 4 +++-
>=C2=A0 4 files changed, 35 insertions(+), 5 deletions(-)
>
> diff --git a/dts/framework/test_suite.py b/dts/framework/test_suite.py=
> index 3b890c0451..3222f172f3 100644
> --- a/dts/framework/test_suite.py
> +++ b/dts/framework/test_suite.py
> @@ -152,7 +152,11 @@ def _configure_ipv4_forwarding(self, enable: bool= ) -> None:
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 self.sut_node.configure_ipv4_forward= ing(enable)
>
>=C2=A0 =C2=A0 =C2=A0 def send_packet_and_capture(
> -=C2=A0 =C2=A0 =C2=A0 =C2=A0 self, packet: Packet, duration: float =3D= 1
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 self,
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 packet: Packet,
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 duration: float =3D 1,
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 no_lldp: bool =3D True,
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 no_arp: bool =3D True,

The parameters of this method are not documented in the docstring, but
we should add these new parameters to the docstring. The same goes for
the other methods (and other parameters of other methods) if they're not overriding an abstract method.

The default should be False if these are meant to be optional, but I
like these defaulting to True, so I'd change the wording from optional<= br> to configurable.

That is a good catch, I didn&= #39;t think about the wording that way but that makes a lot of sense. I wil= l make this change.
=C2=A0

>=C2=A0 =C2=A0 =C2=A0 ) -> list[Packet]:
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 """
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 Send a packet through the appropriat= e interface and
> @@ -162,7 +166,12 @@ def send_packet_and_capture(
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 """
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 packet =3D self._adjust_addresses(pa= cket)
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 return self.tg_node.send_packet_and_= capture(
> -=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 packet, self._tg_port_egres= s, self._tg_port_ingress, duration
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 packet,
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 self._tg_port_egress,
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 self._tg_port_ingress,
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 duration,
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 no_lldp,
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 no_arp,
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 )
>
>=C2=A0 =C2=A0 =C2=A0 def get_expected_packet(self, packet: Packet) ->= ; Packet:
> diff --git a/dts/framework/testbed_model/capturing_traffic_generator.p= y b/dts/framework/testbed_model/capturing_traffic_generator.py
> index ab98987f8e..0a0d0f7d0d 100644
> --- a/dts/framework/testbed_model/capturing_traffic_generator.py
> +++ b/dts/framework/testbed_model/capturing_traffic_generator.py
> @@ -52,6 +52,8 @@ def send_packet_and_capture(
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 send_port: Port,
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 receive_port: Port,
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 duration: float,
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 no_lldp: bool,
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 no_arp: bool,
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 capture_name: str =3D _get_default_c= apture_name(),
>=C2=A0 =C2=A0 =C2=A0 ) -> list[Packet]:
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 """Send a packet, ret= urn received traffic.
> @@ -71,7 +73,7 @@ def send_packet_and_capture(
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0A list of receiv= ed packets. May be empty if no packets are captured.
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 """
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 return self.send_packets_and_capture= (
> -=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 [packet], send_port, receiv= e_port, duration, capture_name
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 [packet], send_port, receiv= e_port, duration, no_lldp, no_arp, capture_name
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 )
>
>=C2=A0 =C2=A0 =C2=A0 def send_packets_and_capture(
> @@ -80,6 +82,8 @@ def send_packets_and_capture(
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 send_port: Port,
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 receive_port: Port,
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 duration: float,
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 no_lldp: bool,
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 no_arp: bool,
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 capture_name: str =3D _get_default_c= apture_name(),
>=C2=A0 =C2=A0 =C2=A0 ) -> list[Packet]:
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 """Send packets, retu= rn received traffic.
> @@ -93,6 +97,8 @@ def send_packets_and_capture(
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 send_port: The egress = port on the TG node.
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 receive_port: The ingr= ess port in the TG node.
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 duration: Capture traf= fic for this amount of time after sending the packets.
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 no_lldp: Option to disable = capturing LLDP packets
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 no_arp: Option to disable c= apturing ARP packets
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 capture_name: The name= of the .pcap file where to store the capture.
>
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 Returns:
> @@ -108,6 +114,8 @@ def send_packets_and_capture(
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 send_port,
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 receive_port,
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 duration,
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 no_lldp,
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 no_arp,
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 )
>
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 self._logger.debug(
> @@ -123,6 +131,8 @@ def _send_packets_and_capture(
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 send_port: Port,
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 receive_port: Port,
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 duration: float,
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 no_lldp: bool,
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 no_arp: bool,
>=C2=A0 =C2=A0 =C2=A0 ) -> list[Packet]:
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 """
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 The extended classes must implement = this method which
> diff --git a/dts/framework/testbed_model/scapy.py b/dts/framework/test= bed_model/scapy.py
> index af0d4dbb25..58f01af21a 100644
> --- a/dts/framework/testbed_model/scapy.py
> +++ b/dts/framework/testbed_model/scapy.py
> @@ -69,6 +69,7 @@ def scapy_send_packets_and_capture(
>=C2=A0 =C2=A0 =C2=A0 send_iface: str,
>=C2=A0 =C2=A0 =C2=A0 recv_iface: str,
>=C2=A0 =C2=A0 =C2=A0 duration: float,
> +=C2=A0 =C2=A0 sniff_filter: str,
>=C2=A0 ) -> list[bytes]:
>=C2=A0 =C2=A0 =C2=A0 """RPC function to send and capture= packets.
>
> @@ -90,6 +91,7 @@ def scapy_send_packets_and_capture(
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 iface=3Drecv_iface,
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 store=3DTrue,
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 started_callback=3Dlambda *args: sca= py.all.sendp(scapy_packets, iface=3Dsend_iface),
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 filter=3Dsniff_filter,
>=C2=A0 =C2=A0 =C2=A0 )
>=C2=A0 =C2=A0 =C2=A0 sniffer.start()
>=C2=A0 =C2=A0 =C2=A0 time.sleep(duration)
> @@ -264,10 +266,16 @@ def _send_packets_and_capture(
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 send_port: Port,
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 receive_port: Port,
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 duration: float,
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 no_lldp: bool,
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 no_arp: bool,
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 capture_name: str =3D _get_default_c= apture_name(),
>=C2=A0 =C2=A0 =C2=A0 ) -> list[Packet]:
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 binary_packets =3D [packet.build() f= or packet in packets]
> -
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 sniff_filter =3D []
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 if no_lldp:
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 sniff_filter.append("e= ther[12:2] !=3D 0x88cc")
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 if no_arp:
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 sniff_filter.append("e= ther[12:2] !=3D 0x0806")
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 xmlrpc_packets: list[
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 xmlrpc.client.Binary >=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 ] =3D self.rpc_server_proxy.scapy_se= nd_packets_and_capture(
> @@ -275,6 +283,7 @@ def _send_packets_and_capture(
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 send_port.logical_name= ,
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 receive_port.logical_n= ame,
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 duration,
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 " && ".jo= in(sniff_filter),
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 )=C2=A0 # type: ignore[assignment] >
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 scapy_packets =3D [Ether(packet.data= ) for packet in xmlrpc_packets]
> diff --git a/dts/framework/testbed_model/tg_node.py b/dts/framework/te= stbed_model/tg_node.py
> index 27025cfa31..98e55b7831 100644
> --- a/dts/framework/testbed_model/tg_node.py
> +++ b/dts/framework/testbed_model/tg_node.py
> @@ -56,6 +56,8 @@ def send_packet_and_capture(
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 send_port: Port,
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 receive_port: Port,
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 duration: float =3D 1,
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 no_lldp: bool =3D True,
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 no_arp: bool =3D True,
>=C2=A0 =C2=A0 =C2=A0 ) -> list[Packet]:
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 """Send a packet, ret= urn received traffic.
>
> @@ -73,7 +75,7 @@ def send_packet_and_capture(
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0A list of receiv= ed packets. May be empty if no packets are captured.
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 """
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 return self.traffic_generator.send_p= acket_and_capture(
> -=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 packet, send_port, receive_= port, duration
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 packet, send_port, receive_= port, duration, no_lldp, no_arp
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 )
>
>=C2=A0 =C2=A0 =C2=A0 def close(self) -> None:
> --
> 2.42.0
>
--0000000000005f247b060a5cfdd6--