Skip to content

Commit

Permalink
python client
Browse files Browse the repository at this point in the history
  • Loading branch information
matanbroner committed May 12, 2023
1 parent 9cd2da2 commit 5519bd1
Show file tree
Hide file tree
Showing 12 changed files with 216 additions and 43 deletions.
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
test
*.db
*.journal
figures/
Expand Down
6 changes: 5 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,9 @@
"files.associations": {
"convert_util.h": "c",
"mptcp.h": "c"
}
},
"[python]": {
"editor.defaultFormatter": "ms-python.python"
},
"python.formatting.provider": "none"
}
11 changes: 8 additions & 3 deletions 5GTC/pkg/performance_logger/plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,24 +64,29 @@ def plot_subflow_features(
# Get subflow data for the feature
subflow_data = [subflow[feature] for subflow in data.values()]

number_iterations = max([len(subflow) for subflow in subflow_data])

# If fx_time, plot the x axis as time (in minutes)
if fx_time:
iterations_per_minute = (60 * 1000) / iteration_ms
# Number of ticks = (ms per iteration) * (number of iterations) all divided by 60,000 ms
num_minutes = ((len(subflow_data[0]) * iteration_ms) / 1000) / 60
num_minutes = ((len(number_iterations) * iteration_ms) / 1000) / 60
# Round up to the nearest minute
num_minutes = int(np.ceil(num_minutes))
ticks_loc = np.arange(
0, len(subflow_data[0]) + iterations_per_minute, iterations_per_minute
0, len(number_iterations) + iterations_per_minute, iterations_per_minute
)
ax.set_xticks(ticks_loc)
ax.set_xticklabels(np.arange(0, num_minutes + 1, 1))
ax.set_xlabel(xlabel if xlabel else "Time (minutes)")
else:
x = np.arange(0, len(subflow_data[0]))
ax.set_xlabel(xlabel if xlabel else "Iteration")
# Plot the data
for i in range(len(subflow_data)):
# Left pad the data with 0s so that all subflows have the same length
subflow_data[i] = np.pad(
subflow_data[i], (0, number_iterations - len(subflow_data[i])), "constant"
)
ax.plot(subflow_data[i])
# Set the title and labels
ax.set_title(title if title else feature)
Expand Down
Empty file added 5GTC/pkg/test/__init__.py
Empty file.
21 changes: 10 additions & 11 deletions 5GTC/pkg/test/test_pyroute2.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import unittest
import pyroute2.netlink.generic.mptcp as mptcp
from test.util import (
from pkg.test.util import (
SysctlContext,
TestMPTCPServer,
TestMPTCPClient,
Expand Down Expand Up @@ -41,16 +41,15 @@ def test_create(self):
ips = interface_ips(DEFAULT_NETWORK_INTERFACE)
ips.remove(DEFAULT_NETWORK_IP)
ip = ips[0]
with SysctlContext('net.mptcp.pm_type', 1):
res = self.mptcp.create(
local_ip=ip,
local_port=0,
local_id=1,
remote_ip=server.ip,
remote_port=server.port,
token=token
)
print(res)
res = self.mptcp.create(
local_ip=ip,
local_port=0,
local_id=1,
remote_ip=server.ip,
remote_port=server.port,
token=token
)
print(res)


# Kill the client and server
Expand Down
8 changes: 6 additions & 2 deletions 5GTC/plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from pkg.performance_logger.db import DB

FIGURES_DIR = os.path.join(os.path.dirname(__file__), "figures")
DB_NAME = "performance_log.db"
DB_PATH = "performance_log.db"
ITERATION_TIME_MS = 500


Expand All @@ -14,7 +14,7 @@ def plot_subflow_info_from_db(
Plot the subflow info from a session_id
"""
is_transfer_rate = field == "transfer_rate"
db = DB({"db_path": DB_NAME, "delete_db_on_exit": False})
db = DB({"db_path": DB_PATH, "delete_db_on_exit": False})
subflows = db.read_all_subflows(iteration_id)
key = "tcpi_bytes_acked" if is_transfer_rate else field
data = {sf[1]: {key: []} for sf in subflows}
Expand Down Expand Up @@ -50,8 +50,12 @@ def plot_subflow_info_from_db(
parser.add_argument("--xlabel", type=str, help="The x-axis label", default=None)
parser.add_argument("--ylabel", type=str, help="The y-axis label", default=None)
parser.add_argument("--title", type=str, help="The title of the plot", default=None)
parser.add_argument("--db-path", type=str, help="The path to the database", default=DB_PATH)
args = parser.parse_args()

if not os.path.exists(FIGURES_DIR):
os.makedirs(FIGURES_DIR)

DB_PATH = args.db_path

plot_subflow_info_from_db(args.iteration_id, args.field, args.xlabel, args.ylabel, args.title)
3 changes: 2 additions & 1 deletion 5GTC/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
flask
matplotlib
numpy
scapy
scapy
pyyaml
16 changes: 10 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ CC = gcc
DEV_CFLAGS = -Wno-unused-variable -Wno-unused-function
CFLAGS = -fPIC -Wall -Wextra $(DEV_CFLAGS)
LDFLAGS = -shared
ENV_NAME = venv

CLIENT_SRCS = lib_convert/convert_client.c lib_convert/convert_util.c
CLIENT_OBJS = $(CLIENT_SRCS:.c=.o)
Expand All @@ -19,14 +20,17 @@ $(CLIENT_TARGET): $(CLIENT_OBJS)
$(CC) $(CFLAGS) -c $< -o $@

setup_transport_converter:
python3 -m venv venv
. venv/bin/activate
pip install -r ./5GTC/requirements.txt
pip install ./5GTC/pkg/mptcp_util
python3 -m venv $(ENV_NAME)
$(ENV_NAME)/bin/pip install --upgrade pip
$(ENV_NAME)/bin/pip install -r ./5GTC/requirements.txt
$(ENV_NAME)/bin/pip install install ./5GTC/pkg/mptcp_util
$(ENV_NAME)/bin/pip install install ./pyroute2

run_transport_converter:
. venv/bin/activate
cd 5GTC && sudo -E python3 main.py
cd 5GTC && ../$(ENV_NAME)/bin/python3 ./main.py

test_transport_converter:
cd 5GTC && sudo -E ../$(ENV_NAME)/bin/python3 -m unittest discover

.PHONY: clean
clean:
Expand Down
Empty file added evaluation/__init__.py
Empty file.
139 changes: 139 additions & 0 deletions evaluation/client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import socket
import logging
import threading
import signal
import time
import sys
import argparse

from evaluation.util import (
generate_random_data_buffer,
MAGIC_NUMBER,
DEFAULT_BUFFER_SIZE,
CLIENT_TYPE_UPLINK,
CLIENT_TYPE_DOWNLINK,
CLIENT_TYPE_ECHO,
COLORS
)

logger = logging.getLogger("mptcp_client")

def log(msg, level="INFO"):
color = COLORS["NONE"]
if level == "INFO":
color = COLORS["GREEN"]
elif level == "WARNING":
color = COLORS["YELLOW"]
elif level == "ERROR":
color = COLORS["RED"]
logger.log(level, "%s%s%s" % (color, msg, COLORS["NONE"]))

class MPTCPClient:
def __init__(self, bind, host, port, client_type=CLIENT_TYPE_ECHO, buffer_size=DEFAULT_BUFFER_SIZE):
self.host = host
self.port = port
self.client_type = client_type
self.buffer_size = buffer_size
self.bind = bind
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_MPTCP)
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.sock.bind(self.bind)
self.sock.connect((self.host, self.port))
self._read_data = b""
self._total_bytes_sent = 0
self._total_bytes_received = 0
self.loop = True
self.authenticate()

def __del__(self):
self.sock.close()
if self.thread:
self.thread.join()

def authenticate(self):
log("Authenticating with server...", "INFO")
# Send magic number
self.sock.send(MAGIC_NUMBER.to_bytes(2, "little"))
# Send cllog("Total bytes sent: %d" % self._total_bytes_sent)
log("Total bytes received: %d" % self._total_bytes_received)
self.sock.send(self.client_type.to_bytes(1, "little"))
# Send buffer size
self.sock.send(self.buffer_size.to_bytes(4, "little"))

def run(self):
# Start a background thread to report metrics
self.thread = threading.Thread(target=self.report_metrics)
self.thread.start()
# Set a SIGINT handler to stop the client
signal.signal(signal.SIGINT, self.stop)
while self.loop:
if self.client_type in [CLIENT_TYPE_DOWNLINK, CLIENT_TYPE_ECHO]:
self.handle_downlink()
if self.client_type in [CLIENT_TYPE_UPLINK, CLIENT_TYPE_ECHO]:
self.handle_uplink()

def stop(self, signum, frame):
self.loop = False
log("Stopping client...", "WARNING")

def handle_downlink(self):
"""
Handle downlink traffic. Store received data in self._read_data.
"""
data = self.sock.recv(self.buffer_size)
self._read_data = data
self._total_bytes_received += len(data)

def handle_uplink(self):
"""
Handle uplink traffic. Send random data from buffer.
"""
if not self._read_data:
data = generate_random_data_buffer(self.buffer_size)
else:
data = self._read_data
self.sock.send(data)
self._total_bytes_sent += len(data)

def report_metrics(self):
"""
Report metrics to stdout.
"""
while self.loop:
log("Total bytes sent: %d" % self._total_bytes_sent, "INFO")
log("Total bytes received: %d" % self._total_bytes_received, "INFO")
time.sleep(1)

if __name__ == "__main__":
if len(sys.argv) < 3:
print("Usage: python3 client.py <host> <port> [--bind <bind>] [--client-type <client-type>] [--buffer-size <buffer-size>]")
print("\t<host>: Host to connect to")
print("\t<port>: Port to connect to")
print("\t--bind <ip>: Bind to this address")
print("\t--client-type <client-type>: Client type [0: uplink, 1: downlink, 2: echo]")
print("\t--buffer-size <buffer-size>: Buffer size")
sys.exit(1)
parser = argparse.ArgumentParser(description="MPTCP Client")
parser.add_argument("host", type=str, help="Host to connect to")
parser.add_argument("port", type=int, help="Port to connect to")
parser.add_argument("--bind", type=str, help="Bind to this address")
parser.add_argument("--client-type", type=int, help="Client type")
parser.add_argument("--buffer-size", type=int, help="Buffer size")
args = parser.parse_args()

if not args.bind:
log("No bind address specified. Using 0.0.0.0", "WARNING")
args.bind = "0.0.0.0"
if not args.client_type or args.client_type not in [CLIENT_TYPE_UPLINK, CLIENT_TYPE_DOWNLINK, CLIENT_TYPE_ECHO]:
log("No client type specified. Using echo client", "WARNING")
args.client_type = CLIENT_TYPE_ECHO
if not args.buffer_size:
log("No buffer size specified. Using default buffer size", "WARNING")
args.buffer_size = DEFAULT_BUFFER_SIZE
(args.bind, 0)
client = MPTCPClient((args.bind, 0), args.host, args.port, args.client_type, args.buffer_size)
client.run()




32 changes: 14 additions & 18 deletions evaluation/server.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,19 @@
import socket
import os
import sys
import threading
import logging
from collections import namedtuple
from evaluation.util import (
MAGIC_NUMBER,
DEFAULT_BUFFER_SIZE,
CLIENT_TYPE_UPLINK,
CLIENT_TYPE_DOWNLINK,
CLIENT_TYPE_ECHO,
generate_random_data_buffer
)

logger = logging.getLogger("mptcp_server")

MAGIC_NUMBER = 0xBEEF

CLIENT_TYPES = {
0: "UPLINK",
1: "DOWNLINK",
2: "ECHO"
}

def generate_random_data_buffer(size):
return os.urandom(size)

ClientConnection = namedtuple("ClientConnection", ["socket", "type", "buffer_size"])

class MPTCPServer:
Expand Down Expand Up @@ -63,21 +59,21 @@ def read_client_header(self, sock):
# Read client type (1 byte)
client_type = sock.recv(1)
client_type = int.from_bytes(client_type, "little")
if client_type not in CLIENT_TYPES:
if client_type not in [CLIENT_TYPE_UPLINK, CLIENT_TYPE_DOWNLINK, CLIENT_TYPE_ECHO]]:
logger.error("Invalid client type: %d" % client_type)
return None
# Read buffer size (4 bytes)
buffer_size = sock.recv(4)
buffer_size = int.from_bytes(buffer_size, "little")
if buffer_size < 0:
logger.error("Invalid buffer size: %d" % buffer_size)
return None
logger.error("Invalid buffer size: %d. Using default buffer size." % buffer_size)
buffer_size = DEFAULT_BUFFER_SIZE
return ClientConnection(sock, client_type, buffer_size)


def handle_client(self, conn: ClientConnection):
# If client is an echo client, just echo the data back as we receive it
if conn.type == 2:
if conn.type == CLIENT_TYPE_ECHO:
# Be the first to send data
data = generate_random_data_buffer(conn.buffer_size)
conn.socket.sendall(data)
Expand All @@ -87,13 +83,13 @@ def handle_client(self, conn: ClientConnection):
break
conn.socket.sendall(data)
# If client is an uplink client, read data from the socket and never send anything back
elif conn.type == 0:
elif conn.type == CLIENT_TYPE_UPLINK:
while True:
data = conn.socket.recv(conn.buffer_size)
if not data:
break
# If client is a downlink client, send random data to the client
elif conn.type == 1:
elif conn.type == CLIENT_TYPE_DOWNLINK:
while True:
data = generate_random_data_buffer(conn.buffer_size)
conn.socket.sendall(data)
Expand Down
22 changes: 22 additions & 0 deletions evaluation/util.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import os

MAGIC_NUMBER = 0xBEEF
DEFAULT_BUFFER_SIZE = 1024

CLIENT_TYPE_UPLINK = 0
CLIENT_TYPE_DOWNLINK = 1
CLIENT_TYPE_ECHO = 2

COLORS = {
"RED": "\033[91m",
"GREEN": "\033[92m",
"YELLOW": "\033[93m",
"BLUE": "\033[94m",
"PURPLE": "\033[95m",
"CYAN": "\033[96m",
"WHITE": "\033[97m",
"NONE": "\033[0m",
}

def generate_random_data_buffer(size):
return os.urandom(size)

0 comments on commit 5519bd1

Please sign in to comment.