From 6bb0c91d10d555ecfa8721ed30addb273c57ca6f Mon Sep 17 00:00:00 2001 From: Louis Scalbert Date: Wed, 23 Oct 2024 12:37:31 +0200 Subject: [PATCH 1/3] tests: apply black to bmpserver Apply black to bmpserver Signed-off-by: Louis Scalbert --- tests/topotests/lib/bmp_collector/bmpserver | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/topotests/lib/bmp_collector/bmpserver b/tests/topotests/lib/bmp_collector/bmpserver index 5257df7530b6..944f299a03c1 100755 --- a/tests/topotests/lib/bmp_collector/bmpserver +++ b/tests/topotests/lib/bmp_collector/bmpserver @@ -5,6 +5,7 @@ # Authored by Farid Mihoub # import argparse + # XXX: something more reliable should be used "Twisted" a great choice. import socket import sys @@ -18,6 +19,7 @@ parser.add_argument("-a", "--address", type=str, default="0.0.0.0") parser.add_argument("-p", "--port", type=int, default=1789) parser.add_argument("-l", "--logfile", type=str, default="/var/log/bmp.log") + def main(): args = parser.parse_args() ADDRESS, PORT = args.address, args.port @@ -43,5 +45,6 @@ def main(): finally: connection.close() + if __name__ == "__main__": sys.exit(main()) From ad6c107f91047ccd8a272516f0f1096fb144090c Mon Sep 17 00:00:00 2001 From: Louis Scalbert Date: Wed, 23 Oct 2024 12:25:42 +0200 Subject: [PATCH 2/3] tests: bmpserver, detect session close immediately bmpserver infinitely loops after the clients has closed the TCP session. In this situation, recv() returns empty data. Detect session close immediately. Fixes: 875511c466 ("topotests: add basic bmp collector") Signed-off-by: Louis Scalbert --- tests/topotests/lib/bmp_collector/bmpserver | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/topotests/lib/bmp_collector/bmpserver b/tests/topotests/lib/bmp_collector/bmpserver index 944f299a03c1..264a281c49bb 100755 --- a/tests/topotests/lib/bmp_collector/bmpserver +++ b/tests/topotests/lib/bmp_collector/bmpserver @@ -34,6 +34,9 @@ def main(): try: while True: data = connection.recv(BGP_MAX_SIZE) + if not data: + # connection closed + break while len(data) > BMPMsg.MIN_LEN: data = BMPMsg.dissect(data, log_file=LOG_FILE) except Exception as e: From c8ed08b22f4b260a0397d035b77b601d6e85bc9d Mon Sep 17 00:00:00 2001 From: Louis Scalbert Date: Wed, 23 Oct 2024 12:26:28 +0200 Subject: [PATCH 3/3] tests: add bmpserver logging Add bmpserver logging Signed-off-by: Louis Scalbert --- tests/topotests/lib/bmp_collector/bmp.py | 21 ++++- tests/topotests/lib/bmp_collector/bmpserver | 92 +++++++++++++++++---- tests/topotests/lib/topogen.py | 22 +++-- 3 files changed, 106 insertions(+), 29 deletions(-) diff --git a/tests/topotests/lib/bmp_collector/bmp.py b/tests/topotests/lib/bmp_collector/bmp.py index f3c0be49c577..ac8af0284499 100644 --- a/tests/topotests/lib/bmp_collector/bmp.py +++ b/tests/topotests/lib/bmp_collector/bmp.py @@ -10,11 +10,13 @@ - XXX: more bmp messages types to dissect - XXX: complete bgp message dissection """ -import datetime import ipaddress import json import os import struct +import sys + +from datetime import datetime from bgp.update import BGPUpdate from bgp.update.rd import RouteDistinguisher @@ -48,6 +50,13 @@ def log2file(logs, log_file): f.write(json.dumps(logs) + "\n") +def timestamp_print(message, file=sys.stderr): + """Helper function to timestamp_print messages with timestamps.""" + + current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + print(f"[{current_time}] {message}", file=file) + + # ------------------------------------------------------------------------------ class BMPCodes: """ @@ -196,14 +205,18 @@ def dissect(cls, data, log_file=None): data = data[msglen:] if version != BMPCodes.VERSION: - # XXX: log something + timestamp_print( + f"Expected BMP version {BMPCodes.VERSION} but got version {version}." + ) return data msg_cls = cls.lookup_msg_type(msgtype) if msg_cls == cls.UNKNOWN_TYPE: - # XXX: log something + timestamp_print(f"Got unknown message type ") return data + timestamp_print(f"Got message type: {msg_cls}") + msg_cls.MSG_LEN = msglen - cls.MIN_LEN logs = msg_cls.dissect(msg_data) logs["seq"] = SEQ @@ -281,7 +294,7 @@ def dissect(cls, data): "peer_distinguisher": str(RouteDistinguisher(peer_distinguisher)), "peer_asn": peer_asn, "peer_bgp_id": peer_bgp_id, - "timestamp": str(datetime.datetime.fromtimestamp(timestamp)), + "timestamp": str(datetime.fromtimestamp(timestamp)), } ) diff --git a/tests/topotests/lib/bmp_collector/bmpserver b/tests/topotests/lib/bmp_collector/bmpserver index 264a281c49bb..56d85fc74b30 100755 --- a/tests/topotests/lib/bmp_collector/bmpserver +++ b/tests/topotests/lib/bmp_collector/bmpserver @@ -7,47 +7,103 @@ import argparse # XXX: something more reliable should be used "Twisted" a great choice. +import signal import socket import sys +from datetime import datetime + from bmp import BMPMsg BGP_MAX_SIZE = 4096 +# Global variable to track shutdown signal +shutdown = False + + parser = argparse.ArgumentParser() parser.add_argument("-a", "--address", type=str, default="0.0.0.0") parser.add_argument("-p", "--port", type=int, default=1789) parser.add_argument("-l", "--logfile", type=str, default="/var/log/bmp.log") +def handle_signal(signum, frame): + global shutdown + timestamp_print(f"Received signal {signum}, shutting down.") + shutdown = True + + +def timestamp_print(message, file=sys.stderr): + """Helper function to timestamp_print messages with timestamps.""" + + current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + print(f"[{current_time}] {message}", file=file) + + def main(): + global shutdown + + # Set up signal handling for SIGTERM and SIGINT + signal.signal(signal.SIGTERM, handle_signal) + signal.signal(signal.SIGINT, handle_signal) + args = parser.parse_args() ADDRESS, PORT = args.address, args.port LOG_FILE = args.logfile + timestamp_print(f"Starting bmpserver on {args.address}:{args.port}") + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - s.bind((ADDRESS, PORT)) - s.listen() - connection, _ = s.accept() - try: - while True: - data = connection.recv(BGP_MAX_SIZE) - if not data: - # connection closed - break - while len(data) > BMPMsg.MIN_LEN: - data = BMPMsg.dissect(data, log_file=LOG_FILE) + s.bind((ADDRESS, PORT)) + s.listen() + timestamp_print(f"Listening on TCP {args.address}:{args.port}") + + connection, client_address = s.accept() + timestamp_print(f"TCP session opened from {client_address}") + + try: + while not shutdown: # Check for shutdown signal + data = connection.recv(BGP_MAX_SIZE) + if shutdown: + break + + if not data: + # connection closed + break + + timestamp_print( + f"Data received from {client_address}: length {len(data)}" + ) + + while len(data) > BMPMsg.MIN_LEN: + data = BMPMsg.dissect(data, log_file=LOG_FILE) + + timestamp_print( + f"Finished dissecting data from {client_address}" + ) + + except Exception as e: + timestamp_print(f"{e}") + pass + except KeyboardInterrupt: + timestamp_print(f"Got Keyboard Interrupt.") + pass + finally: + timestamp_print(f"TCP session closed with {client_address}") + connection.close() + except socket.error as sock_err: + timestamp_print(f"Socket error: {e}") except Exception as e: - # XXX: do something - pass - except KeyboardInterrupt: - # XXX: do something - pass + timestamp_print(f"{e}") finally: - connection.close() + timestamp_print(f"Server shutting down on {ADDRESS}:{PORT}") if __name__ == "__main__": - sys.exit(main()) + try: + sys.exit(main()) + except KeyboardInterrupt: + logging.info("BMP server was interrupted and is shutting down.") + sys.exit(0) diff --git a/tests/topotests/lib/topogen.py b/tests/topotests/lib/topogen.py index 14dd61b077eb..641295258e33 100644 --- a/tests/topotests/lib/topogen.py +++ b/tests/topotests/lib/topogen.py @@ -1273,16 +1273,24 @@ def __str__(self): return gear def start(self, log_file=None): + log_dir = os.path.join(self.logdir, self.name) + self.run("chmod 777 {}".format(log_dir)) + + log_err = os.path.join(log_dir, "bmpserver.log") + log_arg = "-l {}".format(log_file) if log_file else "" - self.run( - "{}/bmp_collector/bmpserver -a {} -p {} {}&".format( - CWD, self.ip, self.port, log_arg - ), - stdout=None, - ) + + with open(log_err, "w") as err: + self.run( + "{}/bmp_collector/bmpserver -a {} -p {} {}&".format( + CWD, self.ip, self.port, log_arg + ), + stdout=None, + stderr=err, + ) def stop(self): - self.run("pkill -9 -f bmpserver") + self.run("pkill -f bmpserver") return ""