SDSM-for-SDI / examples / http3_client.py
http3_client.py
Raw
import argparse
import asyncio
import logging
import os
import pickle
import sys
import ssl
import socket    #DEBUG2 TEST*******************
import time
import csv #in order to write service time data to the csv file
from threading import Thread #this is used to launch the thread for wifi handoff
from coapthon.client.helperclient import HelperClient #used to launch the CoAP client for Wi-Fi Handoff
import random #for wifi handover timing

import multiprocessing as mp
#CoAP Nested Server for handling remote connection migration to client side
from coapthon.server.coap import CoAP
from coapthon.resources.resource import Resource

from collections import deque
from typing import Callable, Deque, Dict, List, Optional, Union, cast
from urllib.parse import urlparse

import wsproto
import wsproto.events
from quic_logger import QuicDirectoryLogger

import aioquic
from aioquic.asyncio.client import connect
from aioquic.asyncio.protocol import QuicConnectionProtocol
from aioquic.h0.connection import H0_ALPN, H0Connection
from aioquic.h3.connection import H3_ALPN, H3Connection
from aioquic.h3.events import (
    DataReceived,
    H3Event,
    HeadersReceived,
    PushPromiseReceived,
)
from aioquic.quic.configuration import QuicConfiguration
from aioquic.quic.events import QuicEvent
from aioquic.tls import CipherSuite, SessionTicket

try:
    import uvloop
except ImportError:
    uvloop = None

logger = logging.getLogger("client")

HttpConnection = Union[H0Connection, H3Connection]

USER_AGENT = "aioquic/" + aioquic.__version__


class URL:
    def __init__(self, url: str) -> None:
        parsed = urlparse(url)

        self.authority = parsed.netloc
        self.full_path = parsed.path
        if parsed.query:
            self.full_path += "?" + parsed.query
        self.scheme = parsed.scheme


class HttpRequest:
    def __init__(
        self, method: str, url: URL, content: bytes = b"", headers: Dict = {}
    ) -> None:
        self.content = content
        self.headers = headers
        self.method = method
        self.url = url


class WebSocket:
    def __init__(
        self, http: HttpConnection, stream_id: int, transmit: Callable[[], None]
    ) -> None:
        self.http = http
        self.queue: asyncio.Queue[str] = asyncio.Queue()
        self.stream_id = stream_id
        self.subprotocol: Optional[str] = None
        self.transmit = transmit
        self.websocket = wsproto.Connection(wsproto.ConnectionType.CLIENT)

    async def close(self, code=1000, reason="") -> None:
        """
        Perform the closing handshake.
        """
        data = self.websocket.send(
            wsproto.events.CloseConnection(code=code, reason=reason)
        )
        self.http.send_data(stream_id=self.stream_id, data=data, end_stream=True)
        self.transmit()

    async def recv(self) -> str:
        """
        Receive the next message.
        """
        return await self.queue.get()

    async def send(self, message: str) -> None:
        """
        Send a message.
        """
        assert isinstance(message, str)

        data = self.websocket.send(wsproto.events.TextMessage(data=message))
        self.http.send_data(stream_id=self.stream_id, data=data, end_stream=False)
        self.transmit()

    def http_event_received(self, event: H3Event) -> None:
        if isinstance(event, HeadersReceived):
            for header, value in event.headers:
                if header == b"sec-websocket-protocol":
                    self.subprotocol = value.decode()
        elif isinstance(event, DataReceived):
            self.websocket.receive_data(event.data)

        for ws_event in self.websocket.events():
            self.websocket_event_received(ws_event)

    def websocket_event_received(self, event: wsproto.events.Event) -> None:
        if isinstance(event, wsproto.events.TextMessage):
            self.queue.put_nowait(event.data)

class HttpClient(QuicConnectionProtocol):
    def __init__(self, *args, **kwargs) -> None:
        super().__init__(*args, **kwargs)

        self.pushes: Dict[int, Deque[H3Event]] = {}
        self._http: Optional[HttpConnection] = None
        self._request_events: Dict[int, Deque[H3Event]] = {}
        self._request_waiter: Dict[int, asyncio.Future[Deque[H3Event]]] = {}
        self._websockets: Dict[int, WebSocket] = {}

        if self._quic.configuration.alpn_protocols[0].startswith("hq-"):
            self._http = H0Connection(self._quic)
        else:
            self._http = H3Connection(self._quic)

    async def get(self, url: str, counter:int, hmstrategy:int, n_request_migration:int, interval_migration:int,  headers: Dict = {} ) -> Deque[H3Event]:   #DEBUG2 TEST* DEBUG V2* #PERF EV AUTOMATION* DEBUG V3*
        """ 
        Perform a GET request.
        """
        return await self._request(
            HttpRequest(method="GET", url=URL(url), headers=headers), counter, hmstrategy, n_request_migration, interval_migration   #DEBUG2 TEST* DEBUG V2* #PERF EV AUTOMATION* DEBUG V3*
        )

    async def post(self, url: str, data: bytes, counter:int, hmstrategy:int, n_request_migration:int, interval_migration:int,  headers: Dict = {} ) -> Deque[H3Event]:   #DEBUG2 TEST* DEBUG V2* #PERF EV AUTOMATION* DEBUG V3*
        """
        Perform a POST request.
        """
        
        #now = time.time() #CARLO
        #print("Inside client.post at: " + str(now)) #CARLO  
  
        return await self._request(
            HttpRequest(method="POST", url=URL(url), content=data, headers=headers), counter, hmstrategy, n_request_migration, interval_migration   #DEBUG2 TEST* DEBUG V2* #PERF EV AUTOMATION* DEBUG V3*
        )

    async def websocket(self, url: str, subprotocols: List[str] = []) -> WebSocket:
        """
        Open a WebSocket.
        """
        request = HttpRequest(method="CONNECT", url=URL(url))
        stream_id = self._quic.get_next_available_stream_id()
        websocket = WebSocket(
            http=self._http, stream_id=stream_id, transmit=self.transmit
        )

        self._websockets[stream_id] = websocket

        headers = [
            (b":method", b"CONNECT"),
            (b":scheme", b"https"),
            (b":authority", request.url.authority.encode()),
            (b":path", request.url.full_path.encode()),
            (b":protocol", b"websocket"),
            (b"user-agent", USER_AGENT.encode()),
            (b"sec-websocket-version", b"13"),
        ]
        if subprotocols:
            headers.append(
                (b"sec-websocket-protocol", ", ".join(subprotocols).encode())
            )
        self._http.send_headers(stream_id=stream_id, headers=headers)

        self.transmit()

        return websocket

    def http_event_received(self, event: H3Event) -> None:
        if isinstance(event, (HeadersReceived, DataReceived)):
            stream_id = event.stream_id
            if stream_id in self._request_events:
                # http
                self._request_events[event.stream_id].append(event)
                if event.stream_ended:
                    request_waiter = self._request_waiter.pop(stream_id)
                    request_waiter.set_result(self._request_events.pop(stream_id))

            elif stream_id in self._websockets:
                # websocket
                websocket = self._websockets[stream_id]
                websocket.http_event_received(event)

            elif event.push_id in self.pushes:
                # push
                self.pushes[event.push_id].append(event)

        elif isinstance(event, PushPromiseReceived):
            self.pushes[event.push_id] = deque()
            self.pushes[event.push_id].append(event)

    def quic_event_received(self, event: QuicEvent) -> None:
        #  pass event to the HTTP layer
        if self._http is not None:
            for http_event in self._http.handle_event(event):
                self.http_event_received(http_event)

    async def _request(self, request: HttpRequest, counter:int, hmstrategy:int, n_request_migration:int, interval_migration:int ):    #DEBUG2 TEST* DEBUG V2* PERF EV AUTOMATION* DEBUG V3*

        #now = time.time() #CARLO
        #print("Inside client._request at: " + str(now)) #CARLO

        stream_id = self._quic.get_next_available_stream_id()
        self._http.send_headers(
            stream_id=stream_id,
            headers=[
                (b":method", request.method.encode()),
                (b":scheme", request.url.scheme.encode()),
                (b":authority", request.url.authority.encode()),
                (b":path", request.url.full_path.encode()),
                (b"user-agent", USER_AGENT.encode()),
            ]
            + [(k.encode(), v.encode()) for (k, v) in request.headers.items()],
        )
        self._http.send_data(stream_id=stream_id, data=request.content, end_stream=True)

        waiter = self._loop.create_future()
        self._request_events[stream_id] = deque()
        self._request_waiter[stream_id] = waiter

        self.transmit(counter = counter, hmstrategy = hmstrategy, n_request_migration = n_request_migration, interval_migration = interval_migration)    #DEBUG2 TEST* DEBUG V2* PERF EV AUTOMATION* DEBUG V3*

        return await asyncio.shield(waiter)


async def perform_http_request(
    client: HttpClient, url: str, data: str, include: bool, output_dir: Optional[str], counter: int, hmstrategy: int, n_request_migration: int, interval_migration: int #DEBUG2 TEST* DEBUG V2* PERF EV AUTOMATION* DEBUG V3*  
) -> None: 
    # perform request

    #CARLO
    if data is not None:
        d = recover_data_from_path(data)

    #start = time.time()
    #print("START NEW REQUEST AT: " + str(start)) #DEBUG2* CARLO

    if data is not None:
        http_events = await client.post(
            url,
            data=d, #data=data.encode(), CARLO
            counter=counter,
            hmstrategy=hmstrategy, #CARLO
            n_request_migration=n_request_migration,
            interval_migration=interval_migration,
            headers={"content-type": "application/x-www-form-urlencoded"},
        )
    else:
        http_events = await client.get(url, counter, hmstrategy, n_request_migration, interval_migration)   #DEBUG2 TEST* DEBUG V2* PERF EV AUTOMATION* DEBUG V3*
    #elapsed = time.time() - start

    # print speed
    octets = 0
    for http_event in http_events:
        if isinstance(http_event, DataReceived):
            octets += len(http_event.data)
    logger.info(
        "Received %d bytes" #CARLO
        % (octets)
    )

    # output response
    if output_dir is not None:
        output_path = os.path.join(
            output_dir, os.path.basename(urlparse(url).path) or "index.html"
        )
        with open(output_path, "wb") as output_file:
            for http_event in http_events:
                if isinstance(http_event, HeadersReceived) and include:
                    headers = b""
                    for k, v in http_event.headers:
                        headers += k + b": " + v + b"\r\n"
                    if headers:
                        output_file.write(headers + b"\r\n")
                elif isinstance(http_event, DataReceived):
                    output_file.write(http_event.data)


def save_session_ticket(ticket: SessionTicket) -> None:
    """
    Callback which is invoked by the TLS engine when a new session ticket
    is received.
    """
    logger.info("New session ticket received")
    if args.session_ticket:
        with open(args.session_ticket, "wb") as fp:
            pickle.dump(ticket, fp)


async def run(
    configuration: QuicConfiguration,
    urls: List[str],
    data: str,
    include: bool,
    output_dir: Optional[str],
    local_port: int,
    zero_rtt: bool,
    n_requests:int, #DEBUG V2
    hmstrategy:int, #DEBUG V2*
    migration_enabled:int, #CARLO
    n_request_handover:int, #CARLO
    n_request_migration:int, #PERF EV AUTOMATION*
    interval_migration:int, #DEBUG V3*
    request_type:int, #DEBUG V4*
    request_interval:int, #DEBUG V4*
) -> None:
    # parse URL
    parsed = urlparse(urls[0])
    assert parsed.scheme in (
        "https",
        "wss",
    ), "Only https:// or wss:// URLs are supported."
    if ":" in parsed.netloc:
        host, port_str = parsed.netloc.split(":")
        port = int(port_str)
    else:
        host = parsed.netloc
        port = 443

    async with connect(
        host,
        port,
        configuration=configuration,
        create_protocol=HttpClient,
        session_ticket_handler=save_session_ticket,
        local_port=local_port,
        wait_connected=not zero_rtt,
    ) as client:
        client = cast(HttpClient, client)

        if parsed.scheme == "wss":
            ws = await client.websocket(urls[0], subprotocols=["chat", "superchat"])

            # send some messages and receive reply
            for i in range(2):
                message = "Hello {}, WebSocket!".format(i)
                print("> " + message)
                await ws.send(message)

                message = await ws.recv()
                print("< " + message)

            await ws.close()
        else:

            # perform request
            cont = 0        #DEBUG*
            have_delay = False #boolean that tells if the current request has exceeded the scheduled starting time, i.e., t > i*T
            coap_server = ["192.168.2.76","192.168.3.76"]
            access_points = ["oem-default-string-2 ","oem-default-string-1 "]
#ssh://server_one/home/osboxes/aioquic-explicit_UniPisa/MigrationInformation.txt
            with open('/data/data/com.termux/files/home/Service_migration/ServiceLatency.csv', 'a') as csvfile:
                spamwriter = csv.writer(csvfile, delimiter=';', quotechar='|', quoting=csv.QUOTE_MINIMAL)
                spamwriter.writerow(["REQ", "LATENCY"])
            
            #diff_timestamp=0 #DEBUG V4*
            clientsideIPQueue = mp.Queue() #create the clientside IP Monitoring Queue
            clientsideIPThread = Thread(target=startCoAPServer, args=(clientsideIPQueue,))
            clientsideIPThread.start()
            clientsideIPMonitoringThread = Thread(target=monitoringRemoteConnectionMigration, args=(clientsideIPQueue, client))
            clientsideIPMonitoringThread.start()
            while(cont < n_requests):  #DEBUG V2

                print("NUMBER REQUEST --> " + str(cont))
                #DEBUG V4*****
                
                if cont == 0: #this is the first request
                    start_time_overall = time.time() #this is the start time of the very first request, used to calculate current_time (t)

                if not have_delay:
                    start_time = time.time() #start time of current request
                else:
                    start_time = cont*request_interval #actual start time of request is always the one in which the request was scheduled

                #if request_type == 1 and diff_timestamp!=0: 
                    #print("CLIENT IS SLEEPING")
                    #await asyncio.sleep(request_interval - diff_timestamp)  #DEBUG*

                #initial_timestamp = time.time() 
                
                #DEBUG V4*****

                
                coros = [
                    perform_http_request(
                        client=client,
                        url=url,
                        data=data,
                        include=include,
                        output_dir=output_dir,
                        counter = cont, #DEBUG2 TEST*
                        hmstrategy = hmstrategy, #DEBUG V2*
                        n_request_migration = n_request_migration, #PERF EV AUTOMATION* 
                        interval_migration = interval_migration, #DEBUG V3
                    )
                    for url in urls
                ]
                await asyncio.gather(*coros)

                finish_time = time.time() #this is the time in which response was received
                elapsed_time = round(finish_time - start_time, 3) #this is the service time experienced by the current request (seconds)
                print("REQ: " +str(cont)+ " TIME: " +str(elapsed_time))

                ##################### write service latency for current request to csv file ##########################

                with open('/data/data/com.termux/files/home/Service_migration/ServiceLatency.csv', 'a') as csvfile:
                    spamwriter = csv.writer(csvfile, delimiter=';', quotechar='|', quoting=csv.QUOTE_MINIMAL)
                    spamwriter.writerow([str(cont), str(elapsed_time)])

                ######################### check if time to perform handover ##########################################

                if cont == n_request_handover and cont != n_requests - 1: #time to perform wifi handover. Do not perform handover when all requests have been performed

                    #wait a random time between 0 and 1s before triggering handover
                    waiting = random.uniform(0,1)
                    time.sleep(waiting)
                    # t1 = Thread(target=clientCoAP, args=(coap_server[0],access_points[0],))
                    # t1.start()
                    list_addr_server = ["192.168.56.8","192.168.56.7"]
                    # t1 = Thread(target=remoteClientCoAP, args=(0,list_addr_server,))
                    # t1.start()
                    # remoteClientCoAP(0,list_addr_server)
                    #update order of access points
                    access_points = list(reversed(access_points))
                    coap_server = list(reversed(coap_server))

                    #update next time of wifi handover
                    n_request_handover = n_request_handover + interval_migration

                ######################### TODO??? save service time in file ##################################################

                current_time = finish_time - start_time_overall #current_time (t) from the very first request performed

                cont+=1

                #check if we are behind schedule with the requests
                if current_time < cont*request_interval: #there is still time before next request should be sent
                    if have_delay:
                        have_delay = False
                    time.sleep(cont*request_interval - current_time) #wait for next scheduled time
                else: #do nothing, i.e., you have a delay; send immediately next request
                    if not have_delay:
                        have_delay = True

                #final_timestamp = time.time() #DEBUG V4*
                #diff_timestamp = final_timestamp - initial_timestamp #DEBUG V4*
 

#CARLO ***** METHOD TO GET PAYLOAD FROM PATH

def recover_data_from_path(path: str) -> bytes:
    with open(path, "r") as f:
        payload = str.encode(f.read())
    return payload

#CARLO ***** METHOD TO GET PAYLOAD FORM PATH


#CARLO ***** METHOD TO PERFORM WIFI HANDOVER
def clientCoAP(coapserver: str,accesspoint: str) -> None:
    try:
        client = HelperClient(server=(coapserver, 5683))
        request = accesspoint
        print(request)
        response = client.put("basic",request)
        #print(response.pretty_print())
        client.stop()
    except Exception as e:
        print("Could not change access point: " +e)

def remoteClientCoAP(mtype: int, list_addr_server) -> None:

    print("Before connecting to CoAP source server")
    client = HelperClient(server=(list_addr_server[0], 5683))
    print("After connecting to CoAP source server")
    request = str(mtype) + ","+str(list_addr_server[0])+","+str(list_addr_server[1])
    print(request)
    
    response = client.put("basic",request)
    print("Received response from CoAP at source server")
    print(response.pretty_print())
    
    client.stop()

#CARLO ***** METHOD TO PERFORM WIFI HANDOVER

class NestedCoAPServer(CoAP):
    def __init__(self, host, port, queue):
        CoAP.__init__(self, (host, port))
        self.queue = queue
        self.add_resource('remoteconnectionmigration/', RemoteConnectionMigration(self.queue))

class RemoteConnectionMigration(Resource):
    def __init__(self, name="RemoteConnectionMigration", coap_server=None, queue=None):
        super(RemoteConnectionMigration, self).__init__(name, coap_server, visible=True,observable=True, allow_children=True)
        self.payload = "Remote Connection Migration"
        self.queue = queue
    def render_GET(self, request):
        return self

    def render_PUT(self, request):
        newIP = request.payload
        # run_command(info)
        self.queue.put(newIP)
        self.payload = "OK"
        return self

    def render_POST(self, request):
        res = RemoteConnectionMigration()
        res.location_query = request.uri_query
        res.payload = request.payload
        return res

    def render_DELETE(self, request):
        return True

def startCoAPServer(queue: mp.Queue) -> None:
    try:
        server = NestedCoAPServer("0.0.0.0", 6365, queue)
        print("After CoAP Nested server declaration")
    except Exception as e:
        print(e)
    try:
        print("Before server listens")
        server.listen(10)
        print("After server listens")
    except Exception as e:
        print(e)
    except KeyboardInterrupt:
        print("Server Shutdown")
        server.close()
        print("Exiting...")

def monitoringRemoteConnectionMigration(queue: mp.Queue, client: HttpClient) -> None:
    while True:
        newPrimaryIP = queue.get()
        if newPrimaryIP:
            print("received new IP address",newPrimaryIP)
            network_path = client._find_network_path(newPrimaryIP)
            client._network_paths.pop()
            client._network_paths.insert(0, network_path) #set to primary


if __name__ == "__main__":
    defaults = QuicConfiguration(is_client=True)

    parser = argparse.ArgumentParser(description="HTTP/3 client")
    parser.add_argument(
        "url", type=str, nargs="+", help="the URL to query (must be HTTPS)"
    )
    parser.add_argument(
        "--ca-certs", type=str, help="load CA certificates from the specified file"
    )
    parser.add_argument(
        "--cipher-suites",
        type=str,
        help="only advertise the given cipher suites, e.g. `AES_256_GCM_SHA384,CHACHA20_POLY1305_SHA256`",
    )
    parser.add_argument(
        "-d", "--data", type=str, help="send the specified data in a POST request"
    )
    parser.add_argument(
        "-i",
        "--include",
        action="store_true",
        help="include the HTTP response headers in the output",
    )
    parser.add_argument(
        "--max-data",
        type=int,
        help="connection-wide flow control limit (default: %d)" % defaults.max_data,
    )
    parser.add_argument(
        "--max-stream-data",
        type=int,
        help="per-stream flow control limit (default: %d)" % defaults.max_stream_data,
    )
    parser.add_argument(
        "-k",
        "--insecure",
        action="store_true",
        help="do not validate server certificate",
    )
    parser.add_argument("--legacy-http", action="store_true", help="use HTTP/0.9")
    parser.add_argument(
        "--output-dir", type=str, help="write downloaded files to this directory",
    )
    parser.add_argument(
        "-q",
        "--quic-log",
        type=str,
        help="log QUIC events to QLOG files in the specified directory",
    )
    parser.add_argument(
        "-l",
        "--secrets-log",
        type=str,
        help="log secrets to a file, for use with Wireshark",
    )
    parser.add_argument(
        "-s",
        "--session-ticket",
        type=str,
        help="read and write session ticket from the specified file",
    )
    parser.add_argument(
        "-v", "--verbose", action="store_true", help="increase logging verbosity"
    )
    parser.add_argument(
        "--local-port", type=int, default=0, help="local port to bind for connections",
    )
    parser.add_argument(
        "--zero-rtt", action="store_true", help="try to send requests using 0-RTT"
    )
    #DEBUG V2*****
    parser.add_argument(
        "--handle_migration_strategy",
        type=int,
        help="Strategy to decide how handle the migration of the server at the client side: 1-FAST: as soon as ack sent --- 0:SLOW: after the first packet lost",
    )
    parser.add_argument(
        "-n",
        "--n_requests",
        type=int,
        help="number of requests made by Client to Server during the connection",
    )
    ######################## CARLO
    parser.add_argument(
        "--migration_enabled",
        type=int,
        help="whether (1) or not (0) container migration is enabled",
    )
    parser.add_argument(
        "--n_request_handover",
        type=int,
        help="At what request from C to S the command to perform wifi handover should be run",
    )
    ####################### CARLO
    #DEBUG V2*****
    #PERF EV AUTOMATION******
    parser.add_argument(
        "--n_request_migration",
        type=int,
        help="At what request from C to S the command to start the migration of the S should be run",
    )
    #PERF EV AUTOMATION******
    #DEBUG V3*****
    parser.add_argument(
        "--interval_migration",
        type=int,
        help="At what frequency the server should migrate in terms of number of requests",
    )
    #DEBUG V3*****
    #DEBUG V4*****
    parser.add_argument(
        "--request_type",
        type=int,
        help="Type of requests from the Client to the Server: 0 --> back to back, 1: interval between each requests",
    )
    parser.add_argument(
        "--request_interval",
        type=int,
        help="Timeout between each request used if request type is different from 0",
    )
    #DEBUG V4*****


    args = parser.parse_args()

    logging.basicConfig(
        format="%(asctime)s %(levelname)s %(name)s %(message)s",
        level=logging.DEBUG if args.verbose else logging.INFO,
    )

    if args.output_dir is not None and not os.path.isdir(args.output_dir):
        raise Exception("%s is not a directory" % args.output_dir)

    # prepare configuration
    configuration = QuicConfiguration(
        is_client=True, alpn_protocols=H0_ALPN if args.legacy_http else H3_ALPN
    )
    if args.ca_certs:
        configuration.load_verify_locations(args.ca_certs)
    if args.cipher_suites:
        configuration.cipher_suites = [
            CipherSuite[s] for s in args.cipher_suites.split(",")
        ]
    if args.insecure:
        configuration.verify_mode = ssl.CERT_NONE
    if args.max_data:
        configuration.max_data = args.max_data
    if args.max_stream_data:
        configuration.max_stream_data = args.max_stream_data
    if args.quic_log:
        configuration.quic_logger = QuicDirectoryLogger(args.quic_log)
    if args.secrets_log:
        configuration.secrets_log_file = open(args.secrets_log, "a")
    if args.session_ticket:
        try:
            with open(args.session_ticket, "rb") as fp:
                configuration.session_ticket = pickle.load(fp)
        except FileNotFoundError:
            pass

    #DEBUG V2*****
    if args.data is not None and args.handle_migration_strategy is None:
        print("You have to insert the migration strategy")
        sys.exit()
    #Update FAST VERSION --> RIMOSSA QUI IL SETTAGGIO DELLA VAR A 0 PERCHè NON UTILIZZATA
    if args.handle_migration_strategy < 0 or args.handle_migration_strategy > 1:
        print("You have to insert the correct type of migration strategy: 1-FAST: as soon as ack sent --- 0:SLOW: after the first packet lost")
        sys.exit()
    if args.data is not None and args.n_requests is None:
        print("You have to insert the number of requests made by C to S during the connection")
        sys.exit()
    #DEBUG V2*****
    #PERF EV AUTOMATION******
    #if args.n_request_migration < 1 or args.n_request_migration >= args.n_requests:
    if args.n_request_migration < 1:
        print("The value of the request when the migration of the S should start has to be greater than 1")
        sys.exit()
    #PERF EV AUTOMATION******
    #DEBUG V3******
    #if args.interval_migration is None or args.interval_migration < 1 or args.interval_migration >= args.n_requests:
        #print("The value of the frequency of the migration of the S should be greater than 1 and less than the number of requests in the whole connection")
        #sys.exit()
    #CARLO
    if args.interval_migration is None or args.interval_migration < 1:
        print("The value of the frequency of the migration is not correct")
        sys.exit()
    if args.migration_enabled and args.n_request_handover != args.n_request_migration - 1:
        print("When container migration is enabled, it must occur at the request that is next to the one when wifi handover is triggered (follow-me-cloud)")
        sys.exit()
    #DEBUG V3******
    #DEBUG V4******
    if args.request_type < 0 or args.request_type > 1:
        print("The value of the type of the request has to be between 0 and 1")
        sys.exit()
    if args.request_interval is None:
        args.request_interval = 0
    if args.request_type != 0 and args.request_interval <= 0:
        print("With this type of request, the interval between request has to be greater than 0")
        sys.exit()
    #DEBUG V4******


    if uvloop is not None:
        uvloop.install()
    loop = asyncio.get_event_loop()
    loop.run_until_complete(
        run(
            configuration=configuration,
            urls=args.url,
            data=args.data,
            include=args.include,
            output_dir=args.output_dir,
            local_port=args.local_port,
            zero_rtt=args.zero_rtt,
            n_requests = args.n_requests,   #DEBUG V2*
            hmstrategy = args.handle_migration_strategy,   #DEBUG V2*
            migration_enabled = args.migration_enabled, #CARLO
            n_request_handover = args.n_request_handover, #CARLO
            n_request_migration = args.n_request_migration, #PERF EV AUTOMATION*
            interval_migration = args.interval_migration, #DEBUG V3*
            request_type = args.request_type, #DEBUG V4*
            request_interval = args.request_interval, #DEBUG V4*
        )
    )