Skip to content

Commit

Permalink
Merge branch 'master' of github.com:matanbroner/5G-Transport-Converter
Browse files Browse the repository at this point in the history
  • Loading branch information
matanbroner committed May 14, 2023
2 parents 1e2fdcd + 6c6f51e commit f92ffef
Show file tree
Hide file tree
Showing 14 changed files with 552 additions and 29 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"
}
33 changes: 20 additions & 13 deletions 5GTC/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,9 @@ def run_nonblocking(self):
logger.debug("Accepted connection from {} with fd={}".format(addr, client_sock.fileno()))
logger.debug(client_sock)

self.handle_connection(client_sock)
success = self.handle_connection(client_sock)
if not success:
client_sock.close()
else:
# Read from the socket
self.read_and_forward(s)
Expand All @@ -103,18 +105,23 @@ def handle_connection(self, client_sock):
Handle a new connection from a client
Parses the Convert header and handles the TLVs
"""
# Read the Convert header from the client (TCP Fast Open)
convert = read_convert_header(client_sock)
if convert is None:
logger.error("Error reading Convert header from client")
return

# Go through the TLVs and handle them
for tlv in convert.tlvs:
if CONVERT_TLVS[tlv.type] == "connect":
self.handle_tlv_connect(tlv, client_sock)
else:
logger.debug("Handled TLV: {}".format(CONVERT_TLVS[tlv.type]))
try:
# Read the Convert header from the client (TCP Fast Open)
convert = read_convert_header(client_sock)
if convert is None:
logger.error("Error reading Convert header from client")
return

# Go through the TLVs and handle them
for tlv in convert.tlvs:
if CONVERT_TLVS[tlv.type] == "connect":
self.handle_tlv_connect(tlv, client_sock)
else:
logger.debug("Handled TLV: {}".format(CONVERT_TLVS[tlv.type]))
return True
except Exception as e:
logger.error("Error handling Convert Protocol header: {}".format(e))
return False

def handle_tlv_connect(self, tlv, client_sock):
"""
Expand Down
12 changes: 9 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], (number_iterations - len(subflow_data[i]), 0), "constant"
)
ax.plot(subflow_data[i])
# Set the title and labels
ax.set_title(title if title else feature)
Expand All @@ -102,6 +107,7 @@ def plot_transfer(subflow_data, iteration_ms=500, subflow_keys=None):
# Create a new figure
fig = Figure()
ax = fig.add_subplot(111)
number_iterations = max([len(subflow_data[subflow]["tcpi_bytes_acked"]) for subflow in subflow_data])
for subflow in subflow_data:
# Calculate the throughput
iterations_per_minute = (60 * 1000) / iteration_ms
Expand Down
Empty file added 5GTC/pkg/test/__init__.py
Empty file.
58 changes: 58 additions & 0 deletions 5GTC/pkg/test/test_pyroute2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Tests for modified Pyroute2 library

import unittest
import pyroute2.netlink.generic.mptcp as mptcp
from pkg.test.util import (
SysctlContext,
TestMPTCPServer,
TestMPTCPClient,
interface_ips,
mptcp_info_get_attr,
DEFAULT_NETWORK_INTERFACE,
DEFAULT_NETWORK_IP
)

class TestMPTCP(unittest.TestCase):
def setUp(self):
self.mptcp = mptcp.MPTCP()

def tearDown(self):
self.mptcp.close()

def test_create(self):
# Sanity check
self.assertIsInstance(self.mptcp, mptcp.MPTCP)

# Check that the default network interface has more than one IP address
self.assertGreater(len(interface_ips(DEFAULT_NETWORK_INTERFACE)), 1)

# Create server
server = TestMPTCPServer(DEFAULT_NETWORK_IP)
server.listen()

# Create the connection
client = TestMPTCPClient(DEFAULT_NETWORK_IP, server.port)
self.assertTrue(client.is_connected())

# Create the new subflow
token = mptcp_info_get_attr(client.sock, 'token')

# Choose an IP that is not the IP used by the client socket already
ips = interface_ips(DEFAULT_NETWORK_INTERFACE)
ips.remove(DEFAULT_NETWORK_IP)
ip = ips[0]
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
client.close()
server.close()

136 changes: 136 additions & 0 deletions 5GTC/pkg/test/util.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import socket
import threading
import subprocess
from pyroute2 import NDB
from mptcp_util import *

IPV4 = 2

# TODO: configure this through command line arguments
DEFAULT_NETWORK_INTERFACE = "enp0s8"
DEFAULT_NETWORK_IP = "192.168.2.221"

class SysctlContext:
"""
Allows execution of code with a temporary sysctl variable value.
Example:
with SysctlContext("net.mptcp.mptcp_enabled", 0):
# Code that runs with mptcp disabled
"""
def __init__(self, key, value):
self.key = key
self.value = value

def __enter__(self):
# Save the current value of the sysctl variable
self.old_value = subprocess.check_output(['sysctl', '-n', self.key]).decode().strip()

# Set the new value of the sysctl variable
subprocess.check_call(['sysctl', '-w', f"{self.key}={self.value}"])

# Read back the value to make sure it was set correctly
new_value = subprocess.check_output(['sysctl', '-n', self.key]).decode().strip()

# Print the old and new values
print(f"sysctl {self.key} was {self.old_value}, now {new_value}")

def __exit__(self, exc_type, exc_val, exc_tb):
# Restore the old value of the sysctl variable
subprocess.check_call(['sysctl', '-w', f"{self.key}={self.old_value}"])

class TestMPTCPServer:
"""
This class is used to create a MPTCP server for testing purposes.
It starts a server in a background thread and listens for incoming connections.
After receiving a connection, it echoes the data back to the client.
"""

def __init__(self, ip):
self.ip = ip
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.ip, 0))
self.port = None

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

def listen(self):
# Start the server in a background thread and store the thread object
self._running = True
self.thread = threading.Thread(target=self._listen)
self.thread.start()
# Wait for the server to start listening, blocking call
while self.port == None:
pass

def close(self):
self._running = False
self.sock.close()
if self.thread:
self.thread.join()

def _listen(self):
self.sock.listen(10)
self.port = self.sock.getsockname()[1]
while self._running:
conn, addr = self.sock.accept()
# Mirror the data back to the client
data = conn.recv(1024)
if not data:
break
conn.sendall(data)
conn.close()


class TestMPTCPClient:
"""
This class is used to create a MPTCP client for testing purposes.
It connects to a given server and sends data to it.
"""
def __init__(self, server_ip, server_port):
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_MPTCP)
self.sock.connect((server_ip, server_port))

def __del__(self):
self.close()

def close(self):
self.sock.close()

def send(self, data):
self.sock.sendall(data)

def is_connected(self):
return self.sock.fileno() != -1

def interface_ips(ifname):
"""
Return a list of IP addresses for the given interface name.
"""
ips = []
with NDB() as ndb:
# Get the interface object by name
iface = ndb.interfaces.get(ifname)

if not iface:
raise Exception("Interface not found: {}".format(ifname))

# Get all IP addresses (both virtual and not) for the interface
for ipaddr in iface.ipaddr:
# If IP address is IPV4, add it to the list
if ipaddr['family'] == IPV4:
ips.append(ipaddr['address'])

return ips

def mptcp_info_get_attr(sock, attr):
"""
Get the value of a MPTCP socket attribute.
"""
# Get the MPTCP connection token
attr = 'mptcpi_' + attr
mptcp_info = get_mptcp_info(sock.fileno())
return mptcp_info[attr]
10 changes: 7 additions & 3 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 @@ -45,13 +45,17 @@ def plot_subflow_info_from_db(

if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Plot the data from a performance log")
parser.add_argument("--iteration_id", type=str, help="The iteration id to plot")
parser.add_argument("--iteration-id", type=str, help="The iteration id to plot")
parser.add_argument("--field", type=str, help="The field to plot")
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)
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.
Loading

0 comments on commit f92ffef

Please sign in to comment.