Last active
June 23, 2023 12:34
-
-
Save tovask/316f0dc855f2459042af403688590a7f to your computer and use it in GitHub Desktop.
Setup an MPTCP test in mininet
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# creating a virtual Ubuntu (14.04) on AWS EC2 (t2.micro) | |
# install MPTCP | |
sudo apt-key adv --keyserver hkp://keys.gnupg.net --recv-keys 379CE192D401AB61 | |
sudo echo -e "\n\n# for MPTCP\ndeb https://dl.bintray.com/cpaasch/deb jessie main\n" >> /etc/apt/sources.list | |
sudo apt-get update | |
sudo apt-get install linux-mptcp | |
# reboot | |
sudo reboot | |
# verify | |
#cat /boot/grub/menu.lst | grep -m 1 mptcp | |
uname -a | |
dmesg | grep MPTCP | |
sysctl net.mptcp | |
# install mininet | |
sudo apt-get install git | |
git clone git://github.com/mininet/mininet | |
mininet/util/install.sh -a | |
# test | |
sudo mn --test pingall |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python | |
""" | |
http://www.multipath-tcp.org/ | |
http://blog.multipath-tcp.org/blog/html/2014/09/16/recommended_multipath_tcp_configuration.html | |
http://blog.multipath-tcp.org/blog/html/2015/02/06/mptcptrace_demo_experiment_five.html | |
http://mininet.org/api/classmininet_1_1net_1_1Mininet.html | |
https://github.com/mininet/mininet/blob/master/mininet/node.py | |
https://www.wireshark.org/docs/man-pages/tshark.html | |
https://www.wireshark.org/docs/dfref/t/tcp.html | |
http://www.packetlevel.ch/html/tcpdumpf.html | |
tshark -G | grep Multipath | |
https://iperf.fr/iperf-doc.php | |
https://github.com/ouya/iperf/ | |
https://docs.python.org/2/library/xml.etree.elementtree.html | |
http://effbot.org/downloads#elementtree | |
https://github.com/remichauvenne/mptcp-model-thesis | |
""" | |
""" | |
questions: | |
- what is a mininet.controller (c0)? why it's not working without that? | |
- why are host trend to be behind a switch? | |
- how to meansure/monitor bandwith/delay? | |
- iperf 'Connection refused' sometimes... | |
- double delay on the first ping | |
- iperf freeze if swith's interface turn down, but good handover if host's intf down | |
- mininet learn between runs, ping breaks arp/handover ?!?!?!?!?!?!?!??? | |
- | |
Notes: | |
- run program on a host: run long process at background and pipe it's output to file, so other short processes can run meanwhile (sendCmd don't block the caller script, but sets waiting to True) | |
""" | |
### CONFIGURATIONS ### | |
#sysctl net.mptcp | |
#sysctl net | grep congestion | |
#sysctl net | grep 'mptcp\|congestion' | |
"congestion controls:" | |
#os.system('sysctl -w net.ipv4.tcp_congestion_control=cubic ') | |
#os.system('modprobe mptcp_coupled && sysctl -w net.ipv4.tcp_congestion_control=lia ') | |
#os.system('modprobe mptcp_olia && sysctl -w net.ipv4.tcp_congestion_control=olia ') | |
#os.system('modprobe mptcp_wvegas && sysctl -w net.ipv4.tcp_congestion_control=wvegas ') | |
#os.system('modprobe mptcp_balia && sysctl -w net.ipv4.tcp_congestion_control=balia ') | |
"path-managers:" | |
#os.system('sysctl -w net.mptcp.mptcp_path_manager=default ') | |
#os.system('sysctl -w net.mptcp.mptcp_path_manager=fullmesh ') | |
#os.system('echo 1 | sudo tee /sys/module/mptcp_fullmesh/parameters/num_subflows') | |
#os.system('modprobe mptcp_ndiffports && sysctl -w net.mptcp.mptcp_path_manager=ndiffports ') | |
#os.system('echo 1 | sudo tee /sys/module/mptcp_ndiffports/parameters/num_subflows') | |
#os.system('modprobe mptcp_binder && sysctl -w net.mptcp.mptcp_path_manager=binder ') | |
"scheduler:" | |
#os.system('sysctl -w net.mptcp.mptcp_scheduler=default ') | |
#os.system('modprobe mptcp_rr && sysctl -w net.mptcp.mptcp_scheduler=roundrobin ') | |
#os.system('echo 1 | sudo tee /sys/module/mptcp_rr/parameters/num_segments') | |
#os.system('echo Y | sudo tee /sys/module/mptcp_rr/parameters/cwnd_limited') | |
#os.system('modprobe mptcp_redundant && sysctl -w net.mptcp.mptcp_scheduler=redundant ') | |
import time | |
import os | |
import sys | |
import re | |
import xml.etree.ElementTree | |
from mininet.net import Mininet | |
from mininet.link import TCLink | |
test_bandwith = True | |
capture_mptcp_headers = False | |
cut_a_link = True | |
add_a_link = True | |
max_queue_size = 100 | |
use_custom_meter = False | |
test_duration = 24 # seconds | |
net = Mininet( cleanup=True ) | |
h1 = net.addHost( 'h1', ip='10.0.1.1') | |
h2 = net.addHost( 'h2', ip='10.0.2.1') | |
s3 = net.addSwitch( 's3' ) | |
c0 = net.addController( 'c0' ) | |
net.addLink( h1, s3, cls=TCLink , bw=5, delay='50ms', max_queue_size=max_queue_size ) | |
net.addLink( h1, s3, cls=TCLink , bw=10, delay='1ms', max_queue_size=max_queue_size ) | |
net.addLink( h2, s3, cls=TCLink , bw=50, delay='1ms', max_queue_size=max_queue_size ) | |
net.addLink( h2, s3, cls=TCLink , bw=50, delay='1ms', max_queue_size=max_queue_size ) | |
h1.setIP('10.0.1.1', intf='h1-eth0') | |
h1.setIP('10.0.1.2', intf='h1-eth1') | |
h2.setIP('10.0.2.1', intf='h2-eth0') | |
h2.setIP('10.0.2.2', intf='h2-eth1') | |
net.start() | |
time.sleep(1) # wait for net to startup (unless this, it might won't work...) | |
print "\n"," "*5,"#"*40,"\n"," "*10,"STARTING\n" | |
if capture_mptcp_headers: | |
print 'starting capturing...', | |
print h2.cmd('tshark -f "tcp" -i any -a duration:'+str(test_duration+1)+' -T pdml '+ | |
' | sed -e "s/30313233343536373839//g" '+ # remove unimportant test data, generated by iperf | |
' | sed -e "s/30:31:32:33:34:35:36:37:38:39://g" '+ | |
' >capt.txt &'), | |
time.sleep(1) # wait for tshark to startup | |
print '...capture started\n' | |
def under_testing(): | |
time.sleep(test_duration/3.0) | |
if cut_a_link: | |
print "\n",'cutting link...', | |
print h1.intf('h1-eth0').ifconfig('down'), | |
print 'link down\n' | |
time.sleep(test_duration/3.0) | |
if add_a_link: | |
print 'adding a new link...', | |
net.addLink( h1, s3, cls=TCLink , bw=50, delay='1ms', max_queue_size=max_queue_size ) | |
s3.attach('s3-eth5') | |
h1.setIP('10.0.1.3', intf='h1-eth2') | |
print 'link added\n' | |
time.sleep(test_duration/3.0) | |
time.sleep(5) # wait (a bit) to finish | |
test_started_timestamp = time.time() | |
if test_bandwith: | |
print 'starting iperf server at',h2.IP() | |
h2.cmd('iperf -s -i 0.5 > iperf_bandwith_server_log.txt & ') # server | |
print 'starting iperf client at',h1.IP(),', connect to ',h2.IP() | |
h1.cmd('iperf -t '+str(test_duration)+' -i 0.5 -c '+h2.IP()+' > iperf_bandwith_client_log.txt &') # cliens | |
under_testing() | |
print "\niperf client response:" | |
print h1.cmd('cat iperf_bandwith_client_log.txt') | |
print "\niperf server response:" | |
print h2.cmd('cat iperf_bandwith_server_log.txt') | |
if capture_mptcp_headers: | |
try: | |
time.sleep(0.5) | |
packets_ip_stat = {} | |
# parse result | |
capture_data = h1.cmd('cat capt.txt') | |
capture_data = capture_data[ capture_data.find('<pdml') : ] | |
capture_data = re.findall( r"<packet>.+?</packet>", capture_data, re.DOTALL) | |
print "capturing ended, result ("+str(len(capture_data))+"):" | |
for i in capture_data: | |
packet = xml.etree.ElementTree.fromstring(i) | |
packet_timestamp = packet.find('proto[@name="geninfo"]').find('field[@name="timestamp"]').get('value') | |
ip_header = packet.find('proto[@name="ip"]') | |
tcp_header = packet.find('proto[@name="tcp"]') | |
src_ip = ip_header.find('field[@name="ip.src"]').get('show') | |
dst_ip = ip_header.find('field[@name="ip.dst"]').get('show') | |
src_port = tcp_header.find('field[@name="tcp.srcport"]').get('show') | |
dst_port = tcp_header.find('field[@name="tcp.dstport"]').get('show') | |
stat_key = src_ip+':'+src_port+' - '+dst_ip+':'+dst_port | |
packets_ip_stat[stat_key] = 0 if (stat_key not in packets_ip_stat) else packets_ip_stat[stat_key]+1 | |
package_info = ("%.2f" % (float(packet_timestamp)-test_started_timestamp)) +"\t"+src_ip+':'+src_port+"\t->\t"+dst_ip+':'+dst_port+"\t" | |
allmptcp = tcp_header.findall('field[@name="tcp.options"]/*/field[@name="tcp.option_kind"][@show="30"]/..') | |
mptcp_subtypes = [] | |
for mptcp in allmptcp: | |
package_info = package_info + mptcp.get('show') + "\t" | |
mptcp_subtype = mptcp.find('field[@name="tcp.options.mptcp.subtype"]').get('value') | |
mptcp_subtypes.append( mptcp_subtype ) | |
package_info = package_info + '(subtype: '+mptcp_subtype | |
if mptcp_subtype == '1': # Join connection | |
addrid = mptcp.find('field[@name="tcp.options.mptcp.addrid"]') | |
if addrid != None: | |
package_info = package_info + ', addrid: '+addrid.get('value') | |
if mptcp_subtype == '3': # Add Address | |
addrid = mptcp.find('field[@name="tcp.options.mptcp.addrid"]') | |
if addrid != None: | |
package_info = package_info + ', addrid: '+addrid.get('value') | |
newaddripv4 = mptcp.find('field[@name="tcp.options.mptcp.ipv4"]') | |
if newaddripv4 != None: | |
package_info = package_info + ', ipv4: '+newaddripv4.get('show') | |
package_info = package_info + ' )' + "\t" | |
if len(mptcp_subtypes)==0 or ( len(mptcp_subtypes)==1 and mptcp_subtypes[0]=='2' ): | |
continue # skip packet contains only data, not interesting | |
print package_info | |
#xml.etree.ElementTree.dump(mptcp) | |
print "\npackets count stat:\n",",\n".join([ (i+' : '+str(packets_ip_stat[i])) for i in sorted(packets_ip_stat.keys())]) | |
except Exception as e: | |
print "\n\nERROR:",e,"\n\n" | |
net.stop() | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<html> | |
<head> | |
<!-- Plotly.js --> | |
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script> | |
<style> | |
html,body{ | |
width: 5500px;/* 12*400+4*100=4800+400=5200 */ | |
} | |
div{ | |
float: left; | |
width: 400px; | |
} | |
div:nth-of-type(3n){ | |
margin-right: 100px; | |
} | |
div:nth-of-type(12n+1){ | |
clear: left; | |
} | |
body:after { | |
content: ""; | |
display: table; | |
clear: both; | |
} | |
</style> | |
</head> | |
<body> | |
<script> | |
<!-- JAVASCRIPT CODE GOES HERE --> | |
function log(msg){console.log(msg);} | |
function loadres(id,title,bgcolor){ | |
var xmlhttp = new XMLHttpRequest(); | |
xmlhttp.onreadystatechange = function(){if(xmlhttp.readyState == XMLHttpRequest.DONE && xmlhttp.status == 200){ | |
var lines = xmlhttp.responseText.split("\n"); | |
var values, time, bandwidth; | |
var res = []; | |
for(i in lines){ | |
values = lines[i].split(" "); | |
time = Number(values[0]); | |
bandwidth = Number(values[1]); | |
if(values.length!=2 || time==NaN || bandwidth==NaN ) | |
continue; | |
res.push({"x":time,"y":bandwidth}); | |
} | |
// try to fix holes and missing end | |
max_interval = 1.0; | |
max_num = 24.9; | |
missing = []; | |
prev = 0; | |
if( res[res.length-1].x < max_num ) // fix the end | |
res.push( {"x":max_num,"y":0} ); | |
for( r in res){ | |
if(res[r].x-prev > max_interval) | |
for(var i = prev+max_interval; i<res[r].x; i+=max_interval ) | |
missing.push( {"x":i,"y":0} ); | |
prev = res[r].x; | |
} | |
res = res.concat(missing); | |
res.sort( function(a, b) {return a.x-b.x} ); | |
var data = { | |
x: [], | |
y: [], | |
line: {color: 'rgb(0, 0, 0)'}, | |
type: 'line' | |
}; | |
for(i in res){ | |
data.x.push(res[i].x); | |
data.y.push(res[i].y); | |
} | |
Plotly.newPlot(document.getElementById(id), [data], { title: title, paper_bgcolor: bgcolor, plot_bgcolor: bgcolor }); | |
}}; | |
xmlhttp.open("GET", "results/"+id+"_bandwith_server_log.csv", true); | |
xmlhttp.send(); | |
} | |
for( cc in {'cubic':0, 'lia':0, 'olia':0, 'wvegas':0, 'balia':0}){ | |
for( pm in {'default':0, 'fullmesh':0, 'ndiffports':0, 'binder':0}){ | |
for( sc in {'default':0, 'roundrobin':0, 'redundant':0}){ | |
id = cc+"_"+pm+"_"+sc; | |
title = cc+" "+pm+" "+sc; | |
div = document.createElement("div"); | |
div.setAttribute('id', id); | |
document.body.appendChild(div); | |
loadres( id, title, {'default':'#FFFF00', 'roundrobin':'#1CE6FF', 'redundant':'#FF34FF'}[sc]); | |
} | |
} | |
} | |
</script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python | |
import os | |
import sys | |
import time | |
import subprocess | |
import re | |
import plotly # https://plot.ly/python/reference/#scatter | |
debug = False | |
def parse_csv(file): | |
rawres = '' | |
with open(file, 'r') as f: | |
rawres = f.read() | |
allres = [] | |
rawres = rawres.split('\n') | |
for line in rawres: | |
if len(line)==0: | |
continue | |
line = line.split(' ') | |
allres.append( ( float(line[0]) , float(line[1]) ) ) | |
allres.sort(key=lambda res: res[0] ) | |
# try to fix holes | |
max_interval = 1.0 | |
max_num = 24.9 | |
missing = [] | |
prev = 0.0 | |
if allres[-1][0] < max_num: # fix the end | |
allres.append( ( max_num, 0.0 ) ) | |
for res in allres: | |
if res[0]-prev > max_interval : | |
# float range | |
for i in [ prev+max_interval+f*max_interval for f in range( int(( res[0] - prev) / max_interval)) ]: | |
missing.append( ( float(i), 0.0 ) ) | |
prev = res[0] | |
allres.extend(missing) | |
allres.sort(key=lambda res: res[0] ) | |
return allres | |
def parse_iperf(file): | |
max_interval = 1.0 | |
rawres = '' | |
with open(file, 'r') as f: | |
rawres = f.read() | |
allres = re.findall( r'\[\s*\d\]\s*([\d.]*)-\s*([\d.]*)\s*sec\s*[\d.]*\s*\w*\s*([\d.]*)\s*(\w*?)/sec' , rawres ) | |
normres = [] | |
for res in allres: | |
if float(res[1]) - float(res[0]) < max_interval: # don't include the summary at the end | |
multiplier = { | |
'bits': 1, | |
'Kbits': 1024, | |
'Mbits': 1024*1024, | |
'Gbits': 1024*1024*1024 | |
}[ res[3] ] | |
normres.append( ( float(res[1]), float(res[2]) * multiplier) ) | |
normres.sort(key=lambda res: res[0] ) | |
return normres | |
def run_test(): | |
os.system('mn -c') # cleanup (to empty node's memories) | |
# run bandwith test | |
os.system('python mptcp_test.py') | |
time.sleep(3) # wait for logs to close (don't ask why) | |
def save_logs(file_name_prefix): | |
csv = parse_iperf('iperf_bandwith_server_log.txt') | |
with open('results/'+file_name_prefix+'_bandwith_server_log.csv', 'w') as f: | |
for row in csv: | |
f.write(str(row[0])+' '+str(row[1])+'\n') | |
csv = parse_iperf('iperf_bandwith_client_log.txt') | |
with open('results/'+file_name_prefix+'_bandwith_client_log.csv', 'w') as f: | |
for row in csv: | |
f.write(str(row[0])+' '+str(row[1])+'\n') | |
os.system('cp iperf_bandwith_server_log.txt results/'+file_name_prefix+'_bandwith_server_log.txt'); | |
os.system('rm iperf_bandwith_server_log.txt'); | |
os.system('cp iperf_bandwith_client_log.txt results/'+file_name_prefix+'_bandwith_client_log.txt'); | |
os.system('rm iperf_bandwith_client_log.txt'); | |
def create_plot(file_name_prefix, cc, pm, sc): | |
server_bandwidth = parse_csv('results/'+file_name_prefix+'_bandwith_server_log.csv') | |
# find out the plot's title | |
title = 'congestion-control: '+cc+', path-manager: '+pm+', scheduler: '+sc+'' | |
print 'create plot:', file_name_prefix, title | |
layout = plotly.graph_objs.Layout(title=title, yaxis = dict(title='bandwith',showgrid=False) ) | |
data = [ | |
{ | |
"x": [float(i[0]) for i in server_bandwidth], # times | |
"y": [float(i[1]) for i in server_bandwidth], # values | |
"name": "server bandwidth", | |
} | |
] | |
fig = plotly.graph_objs.Figure(data=data, layout=layout) | |
plotly.plotly.image.save_as(fig, filename= 'results/'+file_name_prefix+'.png') | |
for cc in [('','cubic'), ('mptcp_coupled','lia'), ('mptcp_olia','olia'), ('mptcp_wvegas','wvegas'), ('mptcp_balia','balia')]: | |
if debug and cc[1]!='olia': continue | |
if cc[0]: | |
os.system('modprobe '+cc[0]) | |
os.system('sysctl -w net.ipv4.tcp_congestion_control='+cc[1]) | |
for pm in [('','default'), ('','fullmesh'), ('mptcp_ndiffports','ndiffports'), ('mptcp_binder','binder')]: | |
if debug and pm[1]!='fullmesh': continue | |
if pm[0]: | |
os.system('modprobe '+pm[0]) | |
os.system('sysctl -w net.mptcp.mptcp_path_manager='+pm[1]) | |
for sc in [('','default'), ('mptcp_rr','roundrobin'), ('mptcp_redundant','redundant')]: | |
if debug and sc[1]!='default': continue | |
if sc[0]: | |
os.system('modprobe '+sc[0]) | |
os.system('sysctl -w net.mptcp.mptcp_scheduler='+sc[1]) | |
file_name_prefix = cc[1]+'_'+pm[1]+'_'+sc[1] | |
print '\n\nstarting:', cc[1],pm[1],sc[1],'\n\n' | |
sys.stdout.flush() # flush the buffer, unless this, the os.system results will be printed before the regular prints | |
run_test() | |
save_logs(file_name_prefix) | |
if os.path.exists('results/'+file_name_prefix+'.png'): # in case the plotter failed at a point, and have to re-run the script, avoid recreate existing plots, | |
print 'plot image already exists, skipping' # since the total generation is limited for one day (max 100 plots) | |
else: | |
create_plot(file_name_prefix, cc[1], pm[1], sc[1]) | |
print 'one fully ended' | |
def generate_html(): | |
f = open('all_images.html','w') | |
f.write('<html><body style="width:5000px;"><style>img{width:350px;height:250px;}</style>') | |
for cc in [('','cubic'), ('mptcp_coupled','lia'), ('mptcp_olia','olia'), ('mptcp_wvegas','wvegas'), ('mptcp_balia','balia')]: | |
for pm in [('','default'), ('','fullmesh'), ('mptcp_ndiffports','ndiffports'), ('mptcp_binder','binder')]: | |
for sc in [('','default'), ('mptcp_rr','roundrobin'), ('mptcp_redundant','redundant')]: | |
file_name_prefix = cc[1]+'_'+pm[1]+'_'+sc[1] | |
f.write('<img src="results/'+file_name_prefix+'.png" />\n') | |
#f.write('<span style="display:inline-block;height:250px;border-left: 2px solid black;"></span>\n') | |
f.write('<hr>\n') | |
f.write('</body></html>') | |
f.close() | |
if not debug: | |
generate_html() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment