"""router topology example for TCP competions.
   Compdelay remains the default version

router between three subnets
h1, h4 and h2 are all linked by the 10.0.0.0/24 subnet.
The purpose of h4 is for starting things on h1 and h2

        +----h1----+
        |          |
  h4----s1         r ---- h3
        |          |
        +----h2----+

For running a TCP competition, consider the runcompetition.sh script
"""

QUEUE=200
DELAY3 = 100		# r--h3 link; units of ms are provided below
DELAY1 = 0		# h1--r link
DELAY2 = 100	# h2--r link
BottleneckBW=12	# mbit
HighBW=100		# mbit


from mininet.net  import Mininet
from mininet.node import Node, OVSKernelSwitch, Controller, RemoteController
from mininet.cli  import CLI
from mininet.link import TCLink
from mininet.topo import Topo
from mininet.log  import setLogLevel, info
import os

#h1addr = '10.0.1.2/24'
#h2addr = '10.0.2.2/24'
#h3addr = '10.0.3.2/24'
#r1addr1= '10.0.1.1/24'
#r1addr2= '10.0.2.1/24'
#r1addr3= '10.0.3.1/24'

class LinuxRouter( Node ):
    "A Node with IP forwarding enabled."

    def config( self, **params ):
        super( LinuxRouter, self).config( **params )
        # Enable forwarding on the router
        info ('enabling forwarding on ', self)
        self.cmd( 'sysctl net.ipv4.ip_forward=1' )

    def terminate( self ):
        self.cmd( 'sysctl net.ipv4.ip_forward=0' )
        super( LinuxRouter, self ).terminate()


class RTopo(Topo):
    #def __init__(self, **kwargs):
    #global r
    def build(self, **_opts):     # special names?
        defaultIP = '10.0.1.1/24'  # IP address for r0-eth1
        r  = self.addNode( 'r', cls=LinuxRouter) # , ip=defaultIP )
        h1 = self.addHost( 'h1', ip='10.0.1.10/24', defaultRoute='via 10.0.1.1' )
        h2 = self.addHost( 'h2', ip='10.0.2.10/24', defaultRoute='via 10.0.2.1' )
        h3 = self.addHost( 'h3', ip='10.0.3.10/24', defaultRoute='via 10.0.3.1' )
        h4 = self.addHost( 'h4', ip='10.0.0.10/24')
        s1 = self.addSwitch('s1')

        #  h1---80Mbit---r---8Mbit/100ms---h2
 
        self.addLink( h1, r, intfName1 = 'h1-eth', intfName2 = 'r-eth1', bw=HighBW)
                 # delay='{}ms'.format(DELAY1))
                 # params2 = {'ip' : '10.0.1.1/24'}, 

        self.addLink( h2, r, intfName1 = 'h2-eth', intfName2 = 'r-eth2', bw=HighBW)
                 # params2 = {'ip' : '10.0.2.1/24'}
                 # delay=DELAY2)

        self.addLink( h3, r, intfName1 = 'h3-eth', intfName2 = 'r-eth3')
                 # params2 = {'ip' : '10.0.3.1/24'}, 
                 # bw=BottleneckBW, delay=DELAY3, queue=QUEUE) 	# apparently queue is IGNORED here.
                 
        self.addLink(h4, s1, intfName1 = 'h4-eth0', intfName2 = 's-eth4')
        self.addLink(h1, s1, intfName1 = 'h1-eth0', intfName2 = 's-eth1')
        self.addLink(h2, s1, intfName1 = 'h2-eth0', intfName2 = 's-eth2')

# delay is the ONE-WAY delay, and is applied only to traffic departing h3-eth.

# BBW=8: 1 KB/ms, for 1K packets; 110 KB in transit
# BBW=10: 1.25 KB/ms, or 50 KB in transit if the delay is 40 ms.
# queue = 267: extra 400 KB in transit, or 8x bandwidthxdelay

def main():
    rtopo = RTopo()
    net = Mininet(topo = rtopo,
                  link=TCLink,
                  #switch = OVSKernelSwitch, 
                  #controller = RemoteController,
		  autoSetMacs = True   # --mac
                )  
    net.start()
    r = net['r']
    r.cmd('ip route list');
    r.cmd('ifconfig r-eth1 10.0.1.1/24')
    r.cmd('ifconfig r-eth2 10.0.2.1/24')
    r.cmd('ifconfig r-eth3 10.0.3.1/24')
    #r.cmd('sysctl net.ipv4.ip_forward=1')
    # now build the qdisc at r-eth3
    set_qdisc(r, 'r-eth3', BottleneckBW, DELAY3, QUEUE)
    #r.cmd('tc qdisc del dev r-eth3 root')
    # rh3capacity=(BottleneckBW/8)*DELAY3/1.5 + 100	# The 100 is a margin of safety
    # r.cmd('tc qdisc add dev r-eth3 root handle 1: netem  delay {}ms limit {}'.format(DELAY3, rh3capacity))
    # r.cmd('tc qdisc add dev r-eth3 parent 1: handle 10: htb default 1')
    # r.cmd('tc class add dev r-eth3 parent 10: classid 10:1 htb rate {}mbit'.format(BottleneckBW))
    # r.cmd('tc qdisc add dev r-eth3 parent 10:1 handle 20: netem limit {}'.format(QUEUE)) 

    h1 = net['h1']
    h2 = net['h2']
    h3 = net['h3']
    h4 = net['h4']

    # h1.cmd('tc qdisc del dev h1-eth root')
    # h1.cmd('tc qdisc add dev h1-eth root fq')
    # h2.cmd('tc qdisc del dev h2-eth root')
    # h2.cmd('tc qdisc add dev h2-eth root fq')
    h1.cmd('ifconfig h1-eth0 10.0.0.1/24')
    h2.cmd('ifconfig h2-eth0 10.0.0.2/24')
    h4.cmd('ifconfig h4-eth0 10.0.0.4/24')
    for h in [r, h1, h2, h3, h4]: h.cmd('/usr/sbin/sshd')

    set_qdisc(h2, 'h2-eth', HighBW, DELAY2, 1000)

    CLI( net)
    net.stop()
    os.system('stty erase \b')

# sets up tc on the interface of the node in question with bw, delay and queue.
# bw in mbit, delay in ms
def set_qdisc(node, eth, bw, delay, queue):
    node.cmd('tc qdisc del dev {} root'.format(eth))
    #print('set_qdisc, node {}, interface {}'.format(node, eth))
    #print(node.cmd('tc -s qdisc show dev {}'.format(eth)))
    link_cap=(bw/8)*delay/1.5 + 100	# The 100 is a margin of safety
    node.cmd('tc qdisc add dev {} root handle 1: netem  delay {}ms limit {}'.format(eth, delay, link_cap))
    node.cmd('tc qdisc add dev {} parent 1: handle 10: htb default 1'.format(eth))
    node.cmd('tc class add dev {} parent 10: classid 10:1 htb rate {}mbit'.format(eth, bw))
    node.cmd('tc qdisc add dev {} parent 10:1 handle 20: netem limit {}'.format(eth, queue)) 
    s=node.cmd('tc -s qdisc show dev {}'.format(eth))
    print('tc -s qdisc show: ', s)

setLogLevel('info')
main()

"""
Default TCLink leads to a queuing hierarchy with an htb node, 5:0, as the root qdisc. 
The class below it is 5:1. Below that is a netem qdisc with handle 10:, with delay WHICH WAY???.
We can change the limit (maximum queue capacity) with:

	tc qdisc change dev r-eth1 handle 10: netem limit 5 delay 110.0ms

check with tc -s qdisc (or tc -s class)
"""
