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 E224A43421; Fri, 1 Dec 2023 19:18:04 +0100 (CET) Received: from mails.dpdk.org (localhost [127.0.0.1]) by mails.dpdk.org (Postfix) with ESMTP id 7B550402E2; Fri, 1 Dec 2023 19:18:04 +0100 (CET) Received: from mail-pj1-f49.google.com (mail-pj1-f49.google.com [209.85.216.49]) by mails.dpdk.org (Postfix) with ESMTP id 4C70F4025F for ; Fri, 1 Dec 2023 19:18:03 +0100 (CET) Received: by mail-pj1-f49.google.com with SMTP id 98e67ed59e1d1-28555b0c7afso2313471a91.1 for ; Fri, 01 Dec 2023 10:18:03 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=iol.unh.edu; s=unh-iol; t=1701454682; x=1702059482; 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=LKlNdQvq3N2+ON8KQgoWUTzbw/tx0N/zVWXG23QEOuU=; b=jlKyVi03XxkWrqoECZxJeWKpKq9l2Dsvk9Cmrra5BuLWEpcmbadHSj1t5szt2Zmapj J1oB1D0qxKztCoujJJM9TBOIU2pdcpaGfXMchEkTS+p7tWlAWRD4GvsIEtrOnIZ3Z7nc CKzdsBeiprtjcJ1bMQvKESZFhOxotRY3A3VBI= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1701454682; x=1702059482; 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=LKlNdQvq3N2+ON8KQgoWUTzbw/tx0N/zVWXG23QEOuU=; b=xRJESxqrqEsXbepN4SvzooJrR79ksf8/6fgyhHVgkm4JyuaS3fMYm6eaOrofb/44zK LyQBCm3QftyCQK5VunjaQkw5kbsGHaA04VFn+HASTirq5bStrKnhBQPqFKX3BkY0jZUq X1xmhD76RaaYOcWTxFjlZZFrN7DZR6IKgSX1AQElkKohrOeQ5IwBFeRgKVcuPszVwzdl 0qOgdUrTOJ0O7WDsZ7HRkyNHS7Fbbm5Xc/E9XZEM8AbMBaZP9I2kXYsQ+xoNLoN0Lp2J NKQVWOdyKVatQyu6zUdvO/o48Frn4u26huhhFlfd0bznK7LJmcQqHWaKG5YCOzsK9cKv V8gg== X-Gm-Message-State: AOJu0YwFVuHE1LwyE7BvRJs0CT/z6l3B0xDPb68apIwRFpt67cn717sr h2LCzGamnO1uhXdd/N/sriRpoFX3kVLH9XLaFB6gmg== X-Google-Smtp-Source: AGHT+IEshAuy03eYghhewNmarSjIRBLHHMUxBWYp7pMAyj8pPxdFdv46iG8zW7EleyqxUk62RqdJjW4Rz5+jXAV8tsI= X-Received: by 2002:a17:90a:1a5d:b0:27f:df1a:caab with SMTP id 29-20020a17090a1a5d00b0027fdf1acaabmr28269539pjl.21.1701454682223; Fri, 01 Dec 2023 10:18:02 -0800 (PST) MIME-Version: 1.0 References: <20231115130959.39420-1-juraj.linkes@pantheon.tech> <20231123151344.162812-1-juraj.linkes@pantheon.tech> <20231123151344.162812-21-juraj.linkes@pantheon.tech> In-Reply-To: <20231123151344.162812-21-juraj.linkes@pantheon.tech> From: Jeremy Spewock Date: Fri, 1 Dec 2023 13:17:51 -0500 Message-ID: Subject: Re: [PATCH v8 20/21] dts: scapy tg docstring update To: =?UTF-8?Q?Juraj_Linke=C5=A1?= Cc: thomas@monjalon.net, Honnappa.Nagarahalli@arm.com, probb@iol.unh.edu, paul.szczepanek@arm.com, yoan.picchi@foss.arm.com, Luca.Vizzarro@arm.com, dev@dpdk.org Content-Type: multipart/alternative; boundary="000000000000ef8ecb060b76c914" 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 --000000000000ef8ecb060b76c914 Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable On Thu, Nov 23, 2023 at 10:14=E2=80=AFAM Juraj Linke=C5=A1 wrote: > Format according to the Google format and PEP257, with slight > deviations. > > Signed-off-by: Juraj Linke=C5=A1 > --- > .../testbed_model/traffic_generator/scapy.py | 91 +++++++++++-------- > 1 file changed, 54 insertions(+), 37 deletions(-) > > diff --git a/dts/framework/testbed_model/traffic_generator/scapy.py > b/dts/framework/testbed_model/traffic_generator/scapy.py > index c88cf28369..30ea3914ee 100644 > --- a/dts/framework/testbed_model/traffic_generator/scapy.py > +++ b/dts/framework/testbed_model/traffic_generator/scapy.py > @@ -2,14 +2,15 @@ > # Copyright(c) 2022 University of New Hampshire > # Copyright(c) 2023 PANTHEON.tech s.r.o. > > -"""Scapy traffic generator. > +"""The Scapy traffic generator. > > -Traffic generator used for functional testing, implemented using the > Scapy library. > +A traffic generator used for functional testing, implemented with > +`the Scapy library `_. > The traffic generator uses an XML-RPC server to run Scapy on the remote > TG node. > > -The XML-RPC server runs in an interactive remote SSH session running > Python console, > -where we start the server. The communication with the server is > facilitated with > -a local server proxy. > +The traffic generator uses the :mod:`xmlrpc.server` module to run an > XML-RPC server > +in an interactive remote Python SSH session. The communication with the > server is facilitated > +with a local server proxy from the :mod:`xmlrpc.client` module. > """ > > import inspect > @@ -69,20 +70,20 @@ def scapy_send_packets_and_capture( > recv_iface: str, > duration: float, > ) -> list[bytes]: > - """RPC function to send and capture packets. > + """The RPC function to send and capture packets. > > - The function is meant to be executed on the remote TG node. > + The function is meant to be executed on the remote TG node via the > server proxy. > > Should this maybe be "This function is meant" instead? I'm not completely sure if it should be, I feel like it might be able to go either way. > Args: > xmlrpc_packets: The packets to send. These need to be converted = to > - xmlrpc.client.Binary before sending to the remote server. > + :class:`~xmlrpc.client.Binary` objects before sending to the > remote server. > send_iface: The logical name of the egress interface. > recv_iface: The logical name of the ingress interface. > duration: Capture for this amount of time, in seconds. > > Returns: > A list of bytes. Each item in the list represents one packet, > which needs > - to be converted back upon transfer from the remote node. > + to be converted back upon transfer from the remote node. > """ > scapy_packets =3D [scapy.all.Packet(packet.data) for packet in > xmlrpc_packets] > sniffer =3D scapy.all.AsyncSniffer( > @@ -96,19 +97,15 @@ def scapy_send_packets_and_capture( > > > def scapy_send_packets(xmlrpc_packets: list[xmlrpc.client.Binary], > send_iface: str) -> None: > - """RPC function to send packets. > + """The RPC function to send packets. > > - The function is meant to be executed on the remote TG node. > - It doesn't return anything, only sends packets. > + The function is meant to be executed on the remote TG node via the > server proxy. > Same thing here. I don't think it matters that much since you refer to it as being "the RPC function" for sending packets, but it feels like you are referring instead to this specific function on this line. > + It only sends `xmlrpc_packets`, without capturing them. > > Args: > xmlrpc_packets: The packets to send. These need to be converted = to > - xmlrpc.client.Binary before sending to the remote server. > + :class:`~xmlrpc.client.Binary` objects before sending to the > remote server. > send_iface: The logical name of the egress interface. > - > - Returns: > - A list of bytes. Each item in the list represents one packet, > which needs > - to be converted back upon transfer from the remote node. > """ > scapy_packets =3D [scapy.all.Packet(packet.data) for packet in > xmlrpc_packets] > scapy.all.sendp(scapy_packets, iface=3Dsend_iface, realtime=3DTrue, > verbose=3DTrue) > @@ -128,11 +125,19 @@ def scapy_send_packets(xmlrpc_packets: > list[xmlrpc.client.Binary], send_iface: s > > > class QuittableXMLRPCServer(SimpleXMLRPCServer): > - """Basic XML-RPC server that may be extended > - by functions serializable by the marshal module. > + """Basic XML-RPC server. > + > + The server may be augmented by functions serializable by the > :mod:`marshal` module. > """ > > def __init__(self, *args, **kwargs): > + """Extend the XML-RPC server initialization. > + > + Args: > + args: The positional arguments that will be passed to the > superclass's constructor. > + kwargs: The keyword arguments that will be passed to the > superclass's constructor. > + The `allow_none` argument will be set to :data:`True`. > + """ > kwargs["allow_none"] =3D True > super().__init__(*args, **kwargs) > self.register_introspection_functions() > @@ -140,13 +145,12 @@ def __init__(self, *args, **kwargs): > self.register_function(self.add_rpc_function) > > def quit(self) -> None: > + """Quit the server.""" > self._BaseServer__shutdown_request =3D True > return None > > def add_rpc_function(self, name: str, function_bytes: > xmlrpc.client.Binary) -> None: > - """Add a function to the server. > - > - This is meant to be executed remotely. > + """Add a function to the server from the local server proxy. > > Args: > name: The name of the function. > @@ -157,6 +161,11 @@ def add_rpc_function(self, name: str, function_bytes= : > xmlrpc.client.Binary) -> N > self.register_function(function) > > def serve_forever(self, poll_interval: float =3D 0.5) -> None: > + """Extend the superclass method with an additional print. > + > + Once executed in the local server proxy, the print gives us a > clear string to expect > + when starting the server. The print means the function was > executed on the XML-RPC server. > + """ > print("XMLRPC OK") > super().serve_forever(poll_interval) > > @@ -164,19 +173,12 @@ def serve_forever(self, poll_interval: float =3D 0.= 5) > -> None: > class ScapyTrafficGenerator(CapturingTrafficGenerator): > """Provides access to scapy functions via an RPC interface. > > - The traffic generator first starts an XML-RPC on the remote TG node. > - Then it populates the server with functions which use the Scapy > library > - to send/receive traffic. > - > - Any packets sent to the remote server are first converted to bytes. > - They are received as xmlrpc.client.Binary objects on the server side= . > - When the server sends the packets back, they are also received as > - xmlrpc.client.Binary object on the client side, are converted back t= o > Scapy > - packets and only then returned from the methods. > + The class extends the base with remote execution of scapy functions. > Same thing here if the above end up getting changed. > > - Arguments: > - tg_node: The node where the traffic generator resides. > - config: The user configuration of the traffic generator. > + Any packets sent to the remote server are first converted to bytes. > They are received as > + :class:`~xmlrpc.client.Binary` objects on the server side. When the > server sends the packets > + back, they are also received as :class:`~xmlrpc.client.Binary` > objects on the client side, are > + converted back to :class:`~scapy.packet.Packet` objects and only the= n > returned from the methods. > > Attributes: > session: The exclusive interactive remote session created by the > Scapy > @@ -190,6 +192,22 @@ class > ScapyTrafficGenerator(CapturingTrafficGenerator): > _config: ScapyTrafficGeneratorConfig > > def __init__(self, tg_node: Node, config: > ScapyTrafficGeneratorConfig): > + """Extend the constructor with Scapy TG specifics. > + > + The traffic generator first starts an XML-RPC on the remote > `tg_node`. > + Then it populates the server with functions which use the Scapy > library > + to send/receive traffic: > + > + * :func:`scapy_send_packets_and_capture` > + * :func:`scapy_send_packets` > + > + To enable verbose logging from the xmlrpc client, use the > :option:`--verbose` > + command line argument or the :envvar:`DTS_VERBOSE` environment > variable. > + > + Args: > + tg_node: The node where the traffic generator resides. > + config: The traffic generator's test run configuration. > + """ > super().__init__(tg_node, config) > > assert ( > @@ -231,10 +249,8 @@ def _start_xmlrpc_server_in_remote_python(self, > listen_port: int) -> None: > # or class, so strip all lines containing only whitespace > src =3D "\n".join([line for line in src.splitlines() if not > line.isspace() and line !=3D ""]) > > - spacing =3D "\n" * 4 > - > # execute it in the python terminal > - self.session.send_command(spacing + src + spacing) > + self.session.send_command(src + "\n") > self.session.send_command( > f"server =3D QuittableXMLRPCServer(('0.0.0.0', > {listen_port}));server.serve_forever()", > "XMLRPC OK", > @@ -267,6 +283,7 @@ def _send_packets_and_capture( > return scapy_packets > > def close(self) -> None: > + """Close the traffic generator.""" > try: > self.rpc_server_proxy.quit() > except ConnectionRefusedError: > -- > 2.34.1 > > --000000000000ef8ecb060b76c914 Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable


<= div dir=3D"ltr" class=3D"gmail_attr">On Thu, Nov 23, 2023 at 10:14=E2=80=AF= AM Juraj Linke=C5=A1 <juraj.linkes@pantheon.tech> wrote:
Format according to the Goog= le format and PEP257, with slight
deviations.

Signed-off-by: Juraj Linke=C5=A1 <juraj.linkes@pantheon.tech>
---
=C2=A0.../testbed_model/traffic_generator/scapy.py=C2=A0 | 91 +++++++++++--= ------
=C2=A01 file changed, 54 insertions(+), 37 deletions(-)

diff --git a/dts/framework/testbed_model/traffic_generator/scapy.py b/dts/f= ramework/testbed_model/traffic_generator/scapy.py
index c88cf28369..30ea3914ee 100644
--- a/dts/framework/testbed_model/traffic_generator/scapy.py
+++ b/dts/framework/testbed_model/traffic_generator/scapy.py
@@ -2,14 +2,15 @@
=C2=A0# Copyright(c) 2022 University of New Hampshire
=C2=A0# Copyright(c) 2023 PANTHEON.tech s.r.o.

-"""Scapy traffic generator.
+"""The Scapy traffic generator.

-Traffic generator used for functional testing, implemented using the Scapy= library.
+A traffic generator used for functional testing, implemented with
+`the Scapy library <https://scapy.readthedocs.io/en/lates= t/>`_.
=C2=A0The traffic generator uses an XML-RPC server to run Scapy on the remo= te TG node.

-The XML-RPC server runs in an interactive remote SSH session running Pytho= n console,
-where we start the server. The communication with the server is facilitate= d with
-a local server proxy.
+The traffic generator uses the :mod:`xmlrpc.server` module to run an XML-R= PC server
+in an interactive remote Python SSH session. The communication with the se= rver is facilitated
+with a local server proxy from the :mod:`xmlrpc.client` module.
=C2=A0"""

=C2=A0import inspect
@@ -69,20 +70,20 @@ def scapy_send_packets_and_capture(
=C2=A0 =C2=A0 =C2=A0recv_iface: str,
=C2=A0 =C2=A0 =C2=A0duration: float,
=C2=A0) -> list[bytes]:
-=C2=A0 =C2=A0 """RPC function to send and capture packets.<= br> +=C2=A0 =C2=A0 """The RPC function to send and capture packe= ts.

-=C2=A0 =C2=A0 The function is meant to be executed on the remote TG node.<= br> +=C2=A0 =C2=A0 The function is meant to be executed on the remote TG node v= ia the server proxy.


Should this maybe be "This function is m= eant" instead? I'm not completely sure if it should be, I feel lik= e it might be able to go either way.
=C2=A0
=C2=A0 =C2=A0 =C2=A0Args:
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0xmlrpc_packets: The packets to send. Thes= e need to be converted to
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 xmlrpc.client.Binary before send= ing to the remote server.
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 :class:`~xmlrpc.client.Binary` o= bjects before sending to the remote server.
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0send_iface: The logical name of the egres= s interface.
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0recv_iface: The logical name of the ingre= ss interface.
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0duration: Capture for this amount of time= , in seconds.

=C2=A0 =C2=A0 =C2=A0Returns:
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0A list of bytes. Each item in the list re= presents one packet, which needs
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 to be converted back upon transf= er from the remote node.
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 to be converted back upon transfer from the re= mote node.
=C2=A0 =C2=A0 =C2=A0"""
=C2=A0 =C2=A0 =C2=A0scapy_packets =3D [scapy.all.Packet(packet.data) for pa= cket in xmlrpc_packets]
=C2=A0 =C2=A0 =C2=A0sniffer =3D scapy.all.AsyncSniffer(
@@ -96,19 +97,15 @@ def scapy_send_packets_and_capture(


=C2=A0def scapy_send_packets(xmlrpc_packets: list[xmlrpc.client.Binary], se= nd_iface: str) -> None:
-=C2=A0 =C2=A0 """RPC function to send packets.
+=C2=A0 =C2=A0 """The RPC function to send packets.

-=C2=A0 =C2=A0 The function is meant to be executed on the remote TG node.<= br> -=C2=A0 =C2=A0 It doesn't return anything, only sends packets.
+=C2=A0 =C2=A0 The function is meant to be executed on the remote TG node v= ia the server proxy.

Same thing here. I don= 9;t think it matters that much since you refer to it as being "the RPC= function" for sending packets, but it feels like you are referring in= stead to this specific function on this line.
=C2=A0
+=C2=A0 =C2=A0 It only sends `xmlrpc_packets`, without capturing them.

=C2=A0 =C2=A0 =C2=A0Args:
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0xmlrpc_packets: The packets to send. Thes= e need to be converted to
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 xmlrpc.client.Binary before send= ing to the remote server.
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 :class:`~xmlrpc.client.Binary` o= bjects before sending to the remote server.
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0send_iface: The logical name of the egres= s interface.
-
-=C2=A0 =C2=A0 Returns:
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 A list of bytes. Each item in the list represe= nts one packet, which needs
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 to be converted back upon transf= er from the remote node.
=C2=A0 =C2=A0 =C2=A0"""
=C2=A0 =C2=A0 =C2=A0scapy_packets =3D [scapy.all.Packet(packet.data) for pa= cket in xmlrpc_packets]
=C2=A0 =C2=A0 =C2=A0scapy.all.sendp(scapy_packets, iface=3Dsend_iface, real= time=3DTrue, verbose=3DTrue)
@@ -128,11 +125,19 @@ def scapy_send_packets(xmlrpc_packets: list[xmlrpc.cl= ient.Binary], send_iface: s


=C2=A0class QuittableXMLRPCServer(SimpleXMLRPCServer):
-=C2=A0 =C2=A0 """Basic XML-RPC server that may be extended<= br> -=C2=A0 =C2=A0 by functions serializable by the marshal module.
+=C2=A0 =C2=A0 """Basic XML-RPC server.
+
+=C2=A0 =C2=A0 The server may be augmented by functions serializable by the= :mod:`marshal` module.
=C2=A0 =C2=A0 =C2=A0"""

=C2=A0 =C2=A0 =C2=A0def __init__(self, *args, **kwargs):
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 """Extend the XML-RPC server in= itialization.
+
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 Args:
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 args: The positional arguments t= hat will be passed to the superclass's constructor.
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 kwargs: The keyword arguments th= at will be passed to the superclass's constructor.
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 The `allow_none` a= rgument will be set to :data:`True`.
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 """
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0kwargs["allow_none"] =3D True =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0super().__init__(*args, **kwargs)
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0self.register_introspection_functions() @@ -140,13 +145,12 @@ def __init__(self, *args, **kwargs):
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0self.register_function(self.add_rpc_funct= ion)

=C2=A0 =C2=A0 =C2=A0def quit(self) -> None:
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 """Quit the server.""= "
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0self._BaseServer__shutdown_request =3D Tr= ue
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0return None

=C2=A0 =C2=A0 =C2=A0def add_rpc_function(self, name: str, function_bytes: x= mlrpc.client.Binary) -> None:
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 """Add a function to the server= .
-
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 This is meant to be executed remotely.
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 """Add a function to the server= from the local server proxy.

=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0Args:
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0name: The name of th= e function.
@@ -157,6 +161,11 @@ def add_rpc_function(self, name: str, function_bytes: = xmlrpc.client.Binary) -> N
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0self.register_function(function)

=C2=A0 =C2=A0 =C2=A0def serve_forever(self, poll_interval: float =3D 0.5) -= > None:
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 """Extend the superclass method= with an additional print.
+
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 Once executed in the local server proxy, the p= rint gives us a clear string to expect
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 when starting the server. The print means the = function was executed on the XML-RPC server.
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 """
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0print("XMLRPC OK")
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0super().serve_forever(poll_interval)

@@ -164,19 +173,12 @@ def serve_forever(self, poll_interval: float =3D 0.5)= -> None:
=C2=A0class ScapyTrafficGenerator(CapturingTrafficGenerator):
=C2=A0 =C2=A0 =C2=A0"""Provides access to scapy functions vi= a an RPC interface.

-=C2=A0 =C2=A0 The traffic generator first starts an XML-RPC on the remote = TG node.
-=C2=A0 =C2=A0 Then it populates the server with functions which use the Sc= apy library
-=C2=A0 =C2=A0 to send/receive traffic.
-
-=C2=A0 =C2=A0 Any packets sent to the remote server are first converted to= bytes.
-=C2=A0 =C2=A0 They are received as xmlrpc.client.Binary objects on the ser= ver side.
-=C2=A0 =C2=A0 When the server sends the packets back, they are also receiv= ed as
-=C2=A0 =C2=A0 xmlrpc.client.Binary object on the client side, are converte= d back to Scapy
-=C2=A0 =C2=A0 packets and only then returned from the methods.
+=C2=A0 =C2=A0 The class extends the base with remote execution of scapy fu= nctions.

Same thing here if the above end up g= etting changed.
=C2=A0

-=C2=A0 =C2=A0 Arguments:
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 tg_node: The node where the traffic generator = resides.
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 config: The user configuration of the traffic = generator.
+=C2=A0 =C2=A0 Any packets sent to the remote server are first converted to= bytes. They are received as
+=C2=A0 =C2=A0 :class:`~xmlrpc.client.Binary` objects on the server side. W= hen the server sends the packets
+=C2=A0 =C2=A0 back, they are also received as :class:`~xmlrpc.client.Binar= y` objects on the client side, are
+=C2=A0 =C2=A0 converted back to :class:`~scapy.packet.Packet` objects and = only then returned from the methods.

=C2=A0 =C2=A0 =C2=A0Attributes:
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0session: The exclusive interactive remote= session created by the Scapy
@@ -190,6 +192,22 @@ class ScapyTrafficGenerator(CapturingTrafficGenerator)= :
=C2=A0 =C2=A0 =C2=A0_config: ScapyTrafficGeneratorConfig

=C2=A0 =C2=A0 =C2=A0def __init__(self, tg_node: Node, config: ScapyTrafficG= eneratorConfig):
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 """Extend the constructor with = Scapy TG specifics.
+
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 The traffic generator first starts an XML-RPC = on the remote `tg_node`.
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 Then it populates the server with functions wh= ich use the Scapy library
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 to send/receive traffic:
+
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 * :func:`scapy_send_packets_and_= capture`
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 * :func:`scapy_send_packets`
+
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 To enable verbose logging from the xmlrpc clie= nt, use the :option:`--verbose`
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 command line argument or the :envvar:`DTS_VERB= OSE` environment variable.
+
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 Args:
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 tg_node: The node where the traf= fic generator resides.
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 config: The traffic generator= 9;s test run configuration.
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 """
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0super().__init__(tg_node, config)

=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0assert (
@@ -231,10 +249,8 @@ def _start_xmlrpc_server_in_remote_python(self, listen= _port: int) -> None:
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0# or class, so strip all lines containing= only whitespace
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0src =3D "\n".join([line for lin= e in src.splitlines() if not line.isspace() and line !=3D ""])
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 spacing =3D "\n" * 4
-
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0# execute it in the python terminal
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 self.session.send_command(spacing + src + spac= ing)
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 self.session.send_command(src + "\n"= )
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0self.session.send_command(
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0f"server =3D Quittable= XMLRPCServer(('0.0.0.0', {listen_port}));server.serve_forever()&quo= t;,
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0"XMLRPC OK",
@@ -267,6 +283,7 @@ def _send_packets_and_capture(
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0return scapy_packets

=C2=A0 =C2=A0 =C2=A0def close(self) -> None:
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 """Close the traffic generator.= """
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0try:
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0self.rpc_server_proxy.quit(= )
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0except ConnectionRefusedError:
--
2.34.1

--000000000000ef8ecb060b76c914--