Skip to content

Instantly share code, notes, and snippets.

@tovask
Last active June 23, 2023 12:34
Show Gist options
  • Save tovask/316f0dc855f2459042af403688590a7f to your computer and use it in GitHub Desktop.
Save tovask/316f0dc855f2459042af403688590a7f to your computer and use it in GitHub Desktop.
Setup an MPTCP test in mininet
# 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
#!/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()
print
<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>
#!/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