Dual-homed Trellis environment
Done:
- Dual homed host with bonding interface
- Dual link between leaf-spine
- Backup link between paired leaves
Not included:
- Dual homed routers
Change-Id: Ib27198a1db13bb257eecf5dc20d997ea4ea643fb
diff --git a/trellis/dhcpd.conf b/trellis/dhcpd.conf
index c3a03e7..035c062 100644
--- a/trellis/dhcpd.conf
+++ b/trellis/dhcpd.conf
@@ -35,3 +35,8 @@
hardware ethernet 00:aa:00:00:00:04;
fixed-address 10.0.3.2;
}
+
+host dh1 {
+ hardware ethernet 00:cc:00:00:00:01;
+ fixed-address 10.0.2.11;
+}
diff --git a/trellis/trellis_dualhome.json b/trellis/trellis_dualhome.json
new file mode 100644
index 0000000..27cc620
--- /dev/null
+++ b/trellis/trellis_dualhome.json
@@ -0,0 +1,240 @@
+{
+ "ports" : {
+ "of:0000000000000203/6" : {
+ "interfaces" : [
+ {
+ "ips" : [ "10.0.2.254/24" ],
+ "vlan-untagged": 20
+ }
+ ]
+ },
+ "of:0000000000000204/6" : {
+ "interfaces" : [
+ {
+ "ips" : [ "10.0.2.254/24" ],
+ "vlan-untagged": 20
+ }
+ ]
+ },
+ "of:0000000000000204/7" : {
+ "interfaces" : [
+ {
+ "ips" : [ "10.0.2.254/24" ],
+ "vlan-untagged": 20
+ }
+ ]
+ },
+ "of:0000000000000204/8" : {
+ "interfaces" : [
+ {
+ "ips" : [ "2000::2ff/120" ],
+ "vlan-untagged": 40
+ }
+ ]
+ },
+ "of:0000000000000204/9" : {
+ "interfaces" : [
+ {
+ "ips" : [ "2000::2ff/120" ],
+ "vlan-untagged": 40
+ }
+ ]
+ },
+ "of:0000000000000204/10" : {
+ "interfaces" : [
+ {
+ "ips" : [ "10.0.2.254/24" ],
+ "vlan-untagged": 20
+ }
+ ]
+ },
+ "of:0000000000000205/6" : {
+ "interfaces" : [
+ {
+ "ips" : [ "10.0.3.254/24" ],
+ "vlan-untagged": 30
+ }
+ ]
+ },
+ "of:0000000000000205/7" : {
+ "interfaces" : [
+ {
+ "ips" : [ "10.0.3.254/24" ],
+ "vlan-untagged": 30
+ }
+ ]
+ },
+ "of:0000000000000205/8" : {
+ "interfaces" : [
+ {
+ "ips" : [ "2000::3ff/120" ],
+ "vlan-untagged": 50
+ }
+ ]
+ },
+ "of:0000000000000205/9" : {
+ "interfaces" : [
+ {
+ "ips" : [ "2000::3ff/120" ],
+ "vlan-untagged": 50
+ }
+ ]
+ },
+ "of:0000000000000205/10" : {
+ "interfaces" : [
+ {
+ "ips" : [ "10.0.3.254/24" ],
+ "vlan-untagged": 30
+ }
+ ]
+ },
+ "of:0000000000000205/11" : {
+ "interfaces" : [
+ {
+ "ips" : [ "10.0.1.254/24", "2000::1ff/120" ],
+ "vlan-untagged": 10
+ }
+ ]
+ },
+ "of:0000000000000205/12" : {
+ "interfaces" : [
+ {
+ "ips" : [ "10.0.1.254/24", "2000::1ff/120" ],
+ "vlan-untagged": 10
+ }
+ ]
+ }
+ },
+ "devices" : {
+ "of:0000000000000203" : {
+ "segmentrouting" : {
+ "name" : "s203",
+ "ipv4NodeSid" : 203,
+ "ipv4Loopback" : "192.168.0.203",
+ "ipv6NodeSid" : 213,
+ "ipv6Loopback" : "2000::c0a8:0203",
+ "routerMac" : "00:00:00:00:02:03",
+ "isEdgeRouter" : true,
+ "adjacencySids" : []
+ },
+ "basic" : {
+ "name": "s203",
+ "driver" : "ofdpa-ovs"
+ }
+ },
+ "of:0000000000000204" : {
+ "segmentrouting" : {
+ "name" : "s204",
+ "ipv4NodeSid" : 204,
+ "ipv4Loopback" : "192.168.0.204",
+ "ipv6NodeSid" : 214,
+ "ipv6Loopback" : "2000::c0a8:0204",
+ "routerMac" : "00:00:00:00:02:03",
+ "isEdgeRouter" : true,
+ "adjacencySids" : []
+ },
+ "basic" : {
+ "name": "s204",
+ "driver" : "ofdpa-ovs"
+ }
+ },
+ "of:0000000000000205" : {
+ "segmentrouting" : {
+ "name" : "s205",
+ "ipv4NodeSid" : 205,
+ "ipv4Loopback" : "192.168.0.205",
+ "ipv6NodeSid" : 215,
+ "ipv6Loopback" : "2000::c0a8:0205",
+ "routerMac" : "00:00:00:00:02:05",
+ "isEdgeRouter" : true,
+ "adjacencySids" : []
+ },
+ "basic" : {
+ "name": "s205",
+ "driver" : "ofdpa-ovs"
+ }
+ },
+ "of:0000000000000206" : {
+ "segmentrouting" : {
+ "name" : "s206",
+ "ipv4NodeSid" : 206,
+ "ipv4Loopback" : "192.168.0.206",
+ "ipv6NodeSid" : 216,
+ "ipv6Loopback" : "2000::c0a8:0206",
+ "routerMac" : "00:00:00:00:02:05",
+ "isEdgeRouter" : true,
+ "adjacencySids" : []
+ },
+ "basic" : {
+ "name": "s206",
+ "driver" : "ofdpa-ovs"
+ }
+ },
+ "of:0000000000000226" : {
+ "segmentrouting" : {
+ "name" : "s226",
+ "ipv4NodeSid" : 226,
+ "ipv4Loopback" : "192.168.0.226",
+ "ipv6NodeSid" : 236,
+ "ipv6Loopback" : "2000::c0a8:0226",
+ "routerMac" : "00:00:00:00:02:26",
+ "isEdgeRouter" : false,
+ "adjacencySids" : []
+ },
+ "basic" : {
+ "name": "s226",
+ "driver" : "ofdpa-ovs"
+ }
+ },
+ "of:0000000000000227" : {
+ "segmentrouting" : {
+ "name" : "s227",
+ "ipv4NodeSid" : 227,
+ "ipv4Loopback" : "192.168.0.227",
+ "ipv6NodeSid" : 237,
+ "ipv6Loopback" : "2000::c0a8:0227",
+ "routerMac" : "00:00:00:00:02:27",
+ "isEdgeRouter" : false,
+ "adjacencySids" : []
+ },
+ "basic" : {
+ "name": "s227",
+ "driver" : "ofdpa-ovs"
+ }
+ }
+ },
+ "hosts": {
+ "00:bb:00:00:00:01/None": {
+ "basic": {
+ "ips": ["2000::201"],
+ "location": "of:0000000000000204/8"
+ }
+ },
+ "00:bb:00:00:00:02/None": {
+ "basic": {
+ "ips": ["2000::202"],
+ "location": "of:0000000000000204/9"
+ }
+ },
+ "00:bb:00:00:00:03/None": {
+ "basic": {
+ "ips": ["2000::301"],
+ "location": "of:0000000000000205/8"
+ }
+ },
+ "00:bb:00:00:00:04/None": {
+ "basic": {
+ "ips": ["2000::302"],
+ "location": "of:0000000000000205/9"
+ }
+ }
+ },
+ "apps" : {
+ "org.onosproject.dhcp-relay" : {
+ "dhcprelay" : {
+ "dhcpserverConnectPoint": "of:0000000000000205/10",
+ "serverip": "10.0.3.253"
+ }
+ }
+ }
+}
diff --git a/trellis/trellis_dualhome.py b/trellis/trellis_dualhome.py
new file mode 100755
index 0000000..1383cc9
--- /dev/null
+++ b/trellis/trellis_dualhome.py
@@ -0,0 +1,193 @@
+#!/usr/bin/python
+
+import sys
+sys.path.append('..')
+from mininet.topo import Topo
+from mininet.net import Mininet
+from mininet.cli import CLI
+from mininet.log import setLogLevel
+from mininet.node import RemoteController, OVSBridge, Host
+from mininet.link import TCLink
+from mininet.nodelib import NAT
+from ipaddress import ip_network
+from routinglib import BgpRouter
+from routinglib import RoutedHost
+
+class Trellis( Topo ):
+ "Trellis basic topology"
+
+ def __init__( self, *args, **kwargs ):
+ Topo.__init__( self, *args, **kwargs )
+
+ # Spines
+ s226 = self.addSwitch('s226', dpid='226')
+ s227 = self.addSwitch('s227', dpid='227')
+
+ # Leaves
+ s203 = self.addSwitch('s203', dpid='203')
+ s204 = self.addSwitch('s204', dpid='204')
+ s205 = self.addSwitch('s205', dpid='205')
+ s206 = self.addSwitch('s206', dpid='206')
+
+ # Leaf-Spine Links
+ self.addLink(s226, s203)
+ self.addLink(s226, s203)
+ self.addLink(s226, s204)
+ self.addLink(s226, s204)
+ self.addLink(s226, s205)
+ self.addLink(s226, s205)
+ self.addLink(s226, s206)
+ self.addLink(s226, s206)
+ self.addLink(s227, s203)
+ self.addLink(s227, s203)
+ self.addLink(s227, s204)
+ self.addLink(s227, s204)
+ self.addLink(s227, s205)
+ self.addLink(s227, s205)
+ self.addLink(s227, s206)
+ self.addLink(s227, s206)
+
+ # Leaf-Leaf Links
+ self.addLink(s203, s204)
+ self.addLink(s205, s206)
+
+ # NOTE avoid using 10.0.1.0/24 which is the default subnet of quaggas
+ # NOTE avoid using 00:00:00:00:00:xx which is the default mac of host behind upstream router
+ # IPv4 Hosts
+ h1 = self.addHost('h1', cls=DhcpClient, mac='00:aa:00:00:00:01')
+ h2 = self.addHost('h2', cls=DhcpClient, mac='00:aa:00:00:00:02')
+ h3 = self.addHost('h3', cls=DhcpClient, mac='00:aa:00:00:00:03')
+ h4 = self.addHost('h4', cls=DhcpClient, mac='00:aa:00:00:00:04')
+ self.addLink(h1, s204)
+ self.addLink(h2, s204)
+ self.addLink(h3, s205)
+ self.addLink(h4, s205)
+
+ # IPv6 Hosts
+ h1v6 = self.addHost('h1v6', cls=RoutedHost, mac='00:bb:00:00:00:01', ips=['2000::201/120'], gateway='2000::2ff')
+ h2v6 = self.addHost('h2v6', cls=RoutedHost, mac='00:bb:00:00:00:02', ips=['2000::202/120'], gateway='2000::2ff')
+ h3v6 = self.addHost('h3v6', cls=RoutedHost, mac='00:bb:00:00:00:03', ips=['2000::301/120'], gateway='2000::3ff')
+ h4v6 = self.addHost('h4v6', cls=RoutedHost, mac='00:bb:00:00:00:04', ips=['2000::302/120'], gateway='2000::3ff')
+ self.addLink(h1v6, s204)
+ self.addLink(h2v6, s204)
+ self.addLink(h3v6, s205)
+ self.addLink(h4v6, s205)
+
+ # Dual-homed IPv4 Hosts
+ dh1 = self.addHost('dh1', cls=DualHomedDhcpClient, mac='00:cc:00:00:00:01')
+ self.addLink(dh1, s204)
+ self.addLink(dh1, s203)
+
+ # DHCP server
+ dhcp = self.addHost('dhcp', cls=DhcpServer, mac='00:99:00:00:00:01', ips=['10.0.3.253/24'], gateway='10.0.3.254')
+ self.addLink(dhcp, s205)
+
+ # Control plane switch (for quagga fpm)
+ cs0 = self.addSwitch('cs0', cls=OVSBridge)
+
+ # Control plane NAT (for quagga fpm)
+ nat = self.addHost('nat', cls=NAT,
+ ip='172.16.0.1/12',
+ subnet=str(ip_network(u'172.16.0.0/12')), inNamespace=False)
+ self.addLink(cs0, nat)
+
+ # Internal Quagga bgp1
+ intfs = {'bgp1-eth0': {'ipAddrs': ['10.0.1.2/24', '2000::102/120'], 'mac': '00:88:00:00:00:02'},
+ 'bgp1-eth1': {'ipAddrs': ['172.16.0.2/12']}}
+ bgp1 = self.addHost('bgp1', cls=BgpRouter,
+ interfaces=intfs,
+ quaggaConfFile='./bgpdbgp1.conf',
+ zebraConfFile='./zebradbgp1.conf')
+ self.addLink(bgp1, s205)
+ self.addLink(bgp1, cs0)
+
+ # External Quagga r1
+ intfs = {'r1-eth0': {'ipAddrs': ['10.0.1.1/24', '2000::101/120'], 'mac': '00:88:00:00:00:01'},
+ 'r1-eth1': {'ipAddrs': ['10.0.99.1/16']},
+ 'r1-eth2': {'ipAddrs': ['2000::9901/120']}}
+ r1 = self.addHost('r1', cls=BgpRouter,
+ interfaces=intfs,
+ quaggaConfFile='./bgpdr1.conf')
+ self.addLink(r1, s205)
+
+ # External IPv4 Host behind r1
+ rh1 = self.addHost('rh1', cls=RoutedHost, ips=['10.0.99.2/24'], gateway='10.0.99.1')
+ self.addLink(r1, rh1)
+
+ # External IPv6 Host behind r1
+ rh1v6 = self.addHost('rh1v6', cls=RoutedHost, ips=['2000::9902/120'], gateway='2000::9901')
+ self.addLink(r1, rh1v6)
+
+topos = { 'trellis' : Trellis }
+
+# TODO extract dhcp classes to a separate library
+class DhcpClient(Host):
+ def __init__(self, name, *args, **kwargs):
+ super(DhcpClient, self).__init__(name, **kwargs)
+ self.pidFile = '/run/dhclient-%s.pid' % self.name
+
+ def config(self, **kwargs):
+ super(DhcpClient, self).config(**kwargs)
+ self.cmd('ip addr flush dev %s' % self.defaultIntf())
+ self.cmd('dhclient -q -4 -nw -pf %s %s' % (self.pidFile, self.defaultIntf()))
+
+ def terminate(self, **kwargs):
+ self.cmd('kill -9 `cat %s`' % self.pidFile)
+ self.cmd('rm -rf %s' % self.pidFile)
+ super(DhcpClient, self).terminate()
+
+class DualHomedDhcpClient(Host):
+ def __init__(self, name, *args, **kwargs):
+ super(DualHomedDhcpClient, self).__init__(name, **kwargs)
+ self.pidFile = '/run/dhclient-%s.pid' % self.name
+ self.bond0 = None
+
+ def config(self, **kwargs):
+ super(DualHomedDhcpClient, self).config(**kwargs)
+ intf0 = self.intfs[0].name
+ intf1 = self.intfs[1].name
+ self.bond0 = "%s-bond0" % self.name
+ self.cmd('modprobe bonding')
+ self.cmd('ip link add %s type bond' % self.bond0)
+ self.cmd('ip link set %s down' % intf0)
+ self.cmd('ip link set %s down' % intf1)
+ self.cmd('ip link set %s master %s' % (intf0, self.bond0))
+ self.cmd('ip link set %s master %s' % (intf1, self.bond0))
+ self.cmd('ip addr flush dev %s' % intf0)
+ self.cmd('ip addr flush dev %s' % intf1)
+ self.cmd('ip link set %s up' % self.bond0)
+ self.cmd('dhclient -q -4 -nw -pf %s %s' % (self.pidFile, self.bond0))
+
+ def terminate(self, **kwargs):
+ self.cmd('ip link set %s down' % self.bond0)
+ self.cmd('ip link delete %s' % self.bond0)
+ self.cmd('kill -9 `cat %s`' % self.pidFile)
+ self.cmd('rm -rf %s' % self.pidFile)
+ super(DualHomedDhcpClient, self).terminate()
+
+class DhcpServer(RoutedHost):
+ binFile = '/usr/sbin/dhcpd'
+ pidFile = '/run/dhcp-server/dhcpd.pid'
+ configFile = './dhcpd.conf'
+
+ def config(self, **kwargs):
+ super(DhcpServer, self).config(**kwargs)
+ self.cmd('%s -q -4 -pf %s -cf %s %s' % (self.binFile, self.pidFile, self.configFile, self.defaultIntf()))
+
+ def terminate(self, **kwargs):
+ self.cmd('kill -9 `cat %s`' % self.pidFile)
+ self.cmd('rm -rf %s' % self.pidFile)
+ super(DhcpServer, self).terminate()
+
+if __name__ == "__main__":
+ setLogLevel('debug')
+ topo = Trellis()
+
+ net = Mininet(topo=topo, controller=None)
+ net.addController(RemoteController('c0', ip='192.168.56.11'))
+ net.addController(RemoteController('c1', ip='192.168.56.12'))
+ net.addController(RemoteController('c2', ip='192.168.56.13'))
+
+ net.start()
+ CLI(net)
+ net.stop()