├── .gitignore ├── LICENSE ├── README.md ├── __init__.py ├── routing.py ├── srv6_mininet_extension.py ├── srv6_net_utils.py ├── srv6_utils.py └── topo └── example_srv6_topology.json /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea/ 3 | *.pyc 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SRv6 Mininet extensions # 2 | 3 | This project creates Mininet networks for testing the SRv6 technology 4 | 5 | ### Prerequisite ### 6 | 7 | This project depends on [Dreamer Topology Parser and Validator](https://github.com/netgroup/Dreamer-Topology-Parser) 8 | 9 | > git clone https://github.com/netgroup/Dreamer-Topology-Parser 10 | > sudo python setup.py install 11 | 12 | This project depends on [SRv6 Properties Generators](https://github.com/netgroup/srv6-properties-generators) 13 | 14 | > git clone https://github.com/netgroup/srv6-properties-generators 15 | > sudo python setup.py install 16 | 17 | ### Run an example experiment ### 18 | 19 | **--help** for usage options: 20 | 21 | Usage: srv6_mininet_extension.py [options] 22 | 23 | Options: 24 | -h, --help show this help message and exit 25 | --controller=CONTROLLER 26 | IP address of the Controller instance 27 | --topology=TOPOLOGY Topology file 28 | 29 | You can start a topology just providing a topology file (relative path): 30 | 31 | > sudo ./srv6_mininet_extensions.py --topology topo/example_srv6_topology.json 32 | 33 | now you have started the topology defined in the file example_srv6_topology.json.json (3 SRv6 routers) 34 | 35 | The file topology.json in the /tmp folder provides a dump of the topology with IPv6 addresses: 36 | 37 | > cat /tmp/topology.json 38 | 39 | for example you can find the mgmt ip of the nodes: 40 | 41 | "id": "sur1", "mgmtip": "2000::3/64" 42 | "id": "ads2", "mgmtip": "2000::2/64" 43 | "id": "ads1", "mgmtip": "2000::1/64", 44 | 45 | or you can get information about links: 46 | 47 | "lhs_intf": "sur1-eth2", "lhs_ip": "2001:0:0:2::2", 48 | "rhs_intf": "ads2-eth2", "rhs_ip": "2001:0:0:2::1", 49 | 50 | Each router has a management interface eth0 connected "out of band" with the host running mininet. You can login on any router using their management IP, for example login in the ads1 router and ping/traceroute the ads2 router: 51 | 52 | # Connect to the node ads1 53 | > ssh root@2000::1 54 | 55 | # Ping ads2 router 2001:0:0:2::1 56 | > ping -6 2001:0:0:2::1 57 | > PING 2001:0:0:2::1(2001:0:0:2::1) 56 data bytes 58 | > 64 bytes from 2001:0:0:2::1: icmp_seq=1 ttl=64 time=5.04 ms 59 | > 64 bytes from 2001:0:0:2::1: icmp_seq=2 ttl=64 time=2.67 ms 60 | > 64 bytes from 2001:0:0:2::1: icmp_seq=3 ttl=64 time=2.72 ms 61 | > 64 bytes from 2001:0:0:2::1: icmp_seq=4 ttl=64 time=2.62 ms 62 | > 64 bytes from 2001:0:0:2::1: icmp_seq=5 ttl=64 time=2.68 ms 63 | 64 | # Traceroute ads2 router 2001:0:0:2::1 65 | > traceroute -6 2001:0:0:2::1 66 | > traceroute to 2001:0:0:2::1 (2001:0:0:2::1), 30 hops max, 80 byte packets 67 | > 1 2001:0:0:2::1 (2001:0:0:2::1) 2.584 ms 2.487 ms 2.408 ms 68 | 69 | Now you can add SRv6 policies: 70 | 71 | # Connect to the ads1 host 72 | > ssh root@2000::1 73 | 74 | # Traffic towards 2001:0:0:d::1 is steered through 2001::3 75 | > ip -6 r a 2001:0:0:2::1/128 encap seg6 mode encap segs 2002::3 dev ads1-eth1 76 | 77 | # Ping ads2 router 2001:0:0:2::1 78 | > ping -6 2001:0:0:2::1 79 | > PING 2001:0:0:2::1(2001:0:0:2::1) 56 data bytes 80 | > 64 bytes from 2001:0:0:2::1: icmp_seq=1 ttl=64 time=1.60 ms 81 | > 64 bytes from 2001:0:0:2::1: icmp_seq=2 ttl=64 time=1.99 ms 82 | > 64 bytes from 2001:0:0:2::1: icmp_seq=3 ttl=64 time=2.00 ms 83 | > 64 bytes from 2001:0:0:2::1: icmp_seq=4 ttl=64 time=1.99 ms 84 | > 64 bytes from 2001:0:0:2::1: icmp_seq=5 ttl=64 time=1.97 ms 85 | 86 | # Traceroute ads2 router 2001:0:0:2::1 87 | > traceroute -6 2001:0:0:2::1 88 | > traceroute to 2001:0:0:2::1 (2001:0:0:2::1), 30 hops max, 80 byte packets 89 | > 1 2001:0:0:1::2 (2001:0:0:1::2) 2.122 ms 2.052 ms 2.031 ms 90 | > 2 2001:0:0:2::1 (2001:0:0:2::1) 1.502 ms 1.485 ms 1.469 ms 91 | 92 | # Dump the traffic on the outgoing interface 93 | > sudo tcpdump -nei ads1-eth2 94 | > tcpdump: verbose output suppressed, use -v or -vv for full protocol decode 95 | > listening on ads1-eth2, link-type EN10MB (Ethernet), capture size 262144 bytes 96 | > 17:41:11.513902 22:9b:8e:4a:77:6f > d2:27:1b:74:c6:f1, ethertype IPv6 (0x86dd), length 182: 2002::1 > 2002::3: srcrt (len=2, type=4, segleft=0[|srcrt] 97 | > 17:41:12.516220 22:9b:8e:4a:77:6f > d2:27:1b:74:c6:f1, ethertype IPv6 (0x86dd), length 182: 2002::1 > 2002::3: srcrt (len=2, type=4, segleft=0[|srcrt] 98 | > 17:41:13.517563 22:9b:8e:4a:77:6f > d2:27:1b:74:c6:f1, ethertype IPv6 (0x86dd), length 182: 2002::1 > 2002::3: srcrt (len=2, type=4, segleft=0[|srcrt] 99 | 100 | # Dump the traffic on the incoming interface 101 | > sudo tcpdump -nei ads1-eth1 102 | tcpdump: verbose output suppressed, use -v or -vv for full protocol decode 103 | listening on ads1-eth1, link-type EN10MB (Ethernet), capture size 262144 bytes 104 | 17:41:00.503046 2a:64:43:95:3c:08 > d6:3b:b2:61:71:8f, ethertype IPv6 (0x86dd), length 118: 2001:0:0:2::1 > 2001::1: ICMP6, echo reply, seq 14, length 64 105 | 17:41:01.503871 2a:64:43:95:3c:08 > d6:3b:b2:61:71:8f, ethertype IPv6 (0x86dd), length 118: 2001:0:0:2::1 > 2001::1: ICMP6, echo reply, seq 15, length 64 106 | 17:41:02.504828 2a:64:43:95:3c:08 > d6:3b:b2:61:71:8f, ethertype IPv6 (0x86dd), length 118: 2001:0:0:2::1 > 2001::1: ICMP6, echo reply, seq 16, length 64 107 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | "Docstring to silence pylint; ignores --ignore option for __init__.py" 2 | -------------------------------------------------------------------------------- /routing.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | ############################################################################################## 4 | # Copyright (C) 2017 Pier Luigi Ventre - (CNIT and University of Rome "Tor Vergata") 5 | # Copyright (C) 2017 Stefano Salsano - (CNIT and University of Rome "Tor Vergata") 6 | # Copyright (C) 2017 Alessandro Masci - (University of Rome "Tor Vergata") 7 | # www.uniroma2.it/netgroup - www.cnit.it 8 | # 9 | # 10 | # Licensed under the Apache License, Version 2.0 (the "License"); 11 | # you may not use this file except in compliance with the License. 12 | # You may obtain a copy of the License at 13 | # 14 | # http://www.apache.org/licenses/LICENSE-2.0 15 | # 16 | # Unless required by applicable law or agreed to in writing, software 17 | # distributed under the License is distributed on an "AS IS" BASIS, 18 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | # See the License for the specific language governing permissions and 20 | # limitations under the License. 21 | # 22 | # Routing module for Segment Routing IPv6 23 | # 24 | # @author Pier Luigi Ventre 25 | # @author Stefano Salsano 26 | # @author Alessandro Masci 27 | 28 | from collections import defaultdict 29 | from mininet.log import info 30 | 31 | import networkx as nx 32 | import time 33 | 34 | # Build shortest path routing for the given topology 35 | class SPFRouting( object ): 36 | 37 | def routing(self, routes, topology, destinations, interfaces_to_ip): 38 | # Init steps 39 | info("Building routing...\n") 40 | # Calculate all the shortest path for the given topology 41 | shortest_paths = nx.all_pairs_shortest_path(topology) 42 | # Iterate over nodes 43 | for node in topology.nodes(data=True): 44 | # Access to data 45 | node_type = node[1]['type'] 46 | # Access to name 47 | node = node[0] 48 | # This node is a server 49 | if node_type == "server": 50 | # Just skip 51 | continue 52 | # Iterate over destinations: 53 | for destination, via in destinations.iteritems(): 54 | # If it is directly attached 55 | if node in via: 56 | # Just skip 57 | continue 58 | # Log the procedure 59 | info("Calculating route from " + node + " -> " + destination + "...") 60 | # Initialize min via 61 | min_via = via[0] 62 | # Iterate over remaining via 63 | for i in range(1, len(via)): 64 | # Get hops of the old via 65 | old_hops = len(shortest_paths[node][min_via]) 66 | # Get hops of the current via 67 | current_hops = len(shortest_paths[node][via[i]]) 68 | # Lower 69 | if current_hops < old_hops: 70 | # Update min_via 71 | min_via = via[i] 72 | # Init route 73 | route = {} 74 | # Save the destination 75 | route["subnet"] = destination 76 | # Get the link from the topology 77 | link_topology = topology[node][shortest_paths[node][min_via][1]] 78 | # Get the gateway. We are assuming no multi-links 79 | gateway = interfaces_to_ip[link_topology[0]["rhs_intf"]] 80 | # Save the gateway 81 | route["gateway"] = gateway 82 | # Get the device 83 | route["device"] = link_topology[0]["lhs_intf"] 84 | # Save the route 85 | routes[node].append(route) 86 | # Log the found via 87 | info("found " + gateway + "\n") 88 | # Done 89 | return routes 90 | -------------------------------------------------------------------------------- /srv6_mininet_extension.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | ############################################################################################## 4 | # Copyright (C) 2017 Pier Luigi Ventre - (CNIT and University of Rome "Tor Vergata") 5 | # Copyright (C) 2017 Stefano Salsano - (CNIT and University of Rome "Tor Vergata") 6 | # Copyright (C) 2017 Alessandro Masci - (University of Rome "Tor Vergata") 7 | # www.uniroma2.it/netgroup - www.cnit.it 8 | # 9 | # 10 | # Licensed under the Apache License, Version 2.0 (the "License"); 11 | # you may not use this file except in compliance with the License. 12 | # You may obtain a copy of the License at 13 | # 14 | # http://www.apache.org/licenses/LICENSE-2.0 15 | # 16 | # Unless required by applicable law or agreed to in writing, software 17 | # distributed under the License is distributed on an "AS IS" BASIS, 18 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | # See the License for the specific language governing permissions and 20 | # limitations under the License. 21 | # 22 | # Mininet scripts for Segment Routing IPv6 23 | # 24 | # @author Pier Luigi Ventre 25 | # @author Stefano Salsano 26 | # @author Alessandro Masci 27 | 28 | from optparse import OptionParser 29 | from collections import defaultdict 30 | 31 | import argparse 32 | import os 33 | import json 34 | import sys 35 | 36 | # IPaddress dependencies 37 | from ipaddress import IPv6Network 38 | import ipaddress 39 | 40 | # Mininet dependencies 41 | from mininet.log import setLogLevel 42 | from mininet.net import Mininet 43 | from mininet.topo import Topo 44 | from mininet.node import RemoteController, OVSBridge, Node 45 | from mininet.link import TCLink 46 | from mininet.cli import CLI 47 | 48 | # NetworkX dependencies 49 | import networkx as nx 50 | from networkx.readwrite import json_graph 51 | 52 | # SRv6 dependencies 53 | from srv6_topo_parser import * 54 | from srv6_utils import * 55 | from srv6_generators import * 56 | from srv6_net_utils import * 57 | 58 | # nodes.sh file for setup of the nodes 59 | NODES_SH = "/tmp/nodes.sh" 60 | # Topology file 61 | TOPOLOGY_FILE = "/tmp/topology.json" 62 | # Mapping node to management address 63 | nodes_to_mgmt = {} 64 | # Network topology 65 | topology = nx.MultiDiGraph() 66 | 67 | # Create SRv6 topology and a management network for the hosts. 68 | class SRv6Topo(Topo): 69 | 70 | # Init of the topology 71 | def __init__( self, topo="", **opts ): 72 | # Parse topology from json file 73 | parser = SRv6TopoParser(topo, verbose=False) 74 | parser.parse_data() 75 | # Save parsed data 76 | self.routers = parser.getRouters() 77 | p_routers_properties = parser.getRoutersProperties() 78 | self.core_links = parser.getCoreLinks() 79 | p_core_links_properties = parser.getCoreLinksProperties() 80 | # Properties generator 81 | generator = PropertiesGenerator() 82 | mgmtAllocator = MgmtAllocator() 83 | # Second step is the generation of the nodes parameters 84 | routers_properties = generator.getRoutersProperties(self.routers) 85 | for router_properties, p_router_properties in zip(routers_properties, p_routers_properties): 86 | p_router_properties['loopback'] = router_properties.loopback 87 | p_router_properties['routerid'] = router_properties.routerid 88 | p_router_properties['mgmtip'] = mgmtAllocator.nextMgmtAddress() 89 | self.routers_properties = p_routers_properties 90 | # Assign mgmt ip to the mgmt station 91 | self.mgmtIP = mgmtAllocator.nextMgmtAddress() 92 | # Third step is the generation of the links parameters 93 | core_links_properties = [] 94 | for core_link in self.core_links: 95 | core_links_properties.append(generator.getLinksProperties([core_link])) 96 | for core_link_properties, p_core_link_properties in zip(core_links_properties, p_core_links_properties): 97 | p_core_link_properties['iplhs'] = core_link_properties[0].iplhs 98 | p_core_link_properties['iprhs'] = core_link_properties[0].iprhs 99 | p_core_link_properties['net'] = core_link_properties[0].net 100 | self.core_links_properties = p_core_links_properties 101 | # Init steps 102 | Topo.__init__( self, **opts ) 103 | 104 | # Build the topology using parser information 105 | def build( self, *args, **params ): 106 | # Mapping nodes to nets 107 | nodes_to_nets = defaultdict(list) 108 | # Init steps 109 | Topo.build( self, *args, **params ) 110 | # Add routers 111 | for router, router_properties in zip(self.routers, self.routers_properties): 112 | # Assign mgmtip, loobackip, routerid 113 | mgmtIP = router_properties['mgmtip'] 114 | loopbackIP = router_properties['loopback'] 115 | routerid = router_properties['routerid'] 116 | loopbackip = "%s/%s" % (loopbackIP, LoopbackAllocator.prefix) 117 | mgmtip = "%s/%s" % (mgmtIP, MgmtAllocator.prefix) 118 | # Add the router to the topology 119 | self.addHost(name=router, cls=SRv6Router, sshd=True, mgmtip=mgmtip, 120 | loopbackip=loopbackip, routerid=routerid, nets=[]) 121 | # Save mapping node to mgmt 122 | nodes_to_mgmt[router] = str(mgmtIP) 123 | # Add node to the topology graph 124 | topology.add_node(router, mgmtip=mgmtip , loopbackip=loopbackip, 125 | routerid=routerid, type="router") 126 | # Create the mgmt switch 127 | br_mgmt = self.addSwitch(name='br-mgmt1', cls=OVSBridge) 128 | # Assign the mgmt ip to the mgmt station 129 | mgmtIP = self.mgmtIP 130 | mgmtip = "%s/%s" % (mgmtIP, MgmtAllocator.prefix) 131 | # Mgmt name 132 | mgmt = 'mgmt' 133 | # Create the mgmt node in the root namespace 134 | self.addHost(name=mgmt, cls=SRv6Router, sshd=False, mgmtip=mgmtip, 135 | inNamespace=False) 136 | nodes_to_mgmt[mgmt] = str(mgmtIP) 137 | # Create a link between mgmt switch and mgmt station 138 | self.addLink(mgmt, br_mgmt, bw=1000, delay=0) 139 | # Connect all the routers to the management network 140 | for router in self.routers: 141 | # Create a link between mgmt switch and the router 142 | self.addLink(router, br_mgmt, bw=1000, delay=0) 143 | # Iterate over the core links and generate them 144 | for core_link, core_link_properties in zip(self.core_links, self.core_links_properties): 145 | # Get the left hand side of the pair 146 | lhs = core_link[0] 147 | # Get the right hand side of the pair 148 | rhs = core_link[1] 149 | # Create the core link 150 | self.addLink(lhs, rhs, bw=core_link_properties['bw'], 151 | delay=core_link_properties['delay']) 152 | # Get Port number 153 | portNumber = self.port(lhs, rhs) 154 | # Create lhs_intf 155 | lhsintf = "%s-eth%d" % (lhs, portNumber[0]) 156 | # Create rhs_intf 157 | rhsintf = "%s-eth%d" % (rhs, portNumber[1]) 158 | # Assign a data-plane net to this link 159 | net = core_link_properties['net'] 160 | # Get lhs ip 161 | lhsip = "%s/%d" % (core_link_properties['iplhs'], NetAllocator.prefix) 162 | # Get rhs ip 163 | rhsip = "%s/%d" % (core_link_properties['iprhs'], NetAllocator.prefix) 164 | # Add edge to the topology 165 | topology.add_edge(lhs, rhs, lhs_intf=lhsintf, rhs_intf=rhsintf, lhs_ip=lhsip, rhs_ip=rhsip) 166 | # Add the reverse edge to the topology 167 | topology.add_edge(rhs, lhs, lhs_intf=rhsintf, rhs_intf=lhsintf, lhs_ip=rhsip, rhs_ip=lhsip) 168 | # Save net 169 | lhsnet = {'intf':lhsintf, 'ip':lhsip, 'net':net} 170 | rhsnet = {'intf':rhsintf, 'ip':rhsip, 'net':net} 171 | self.nodeInfo(lhs)['nets'].append(lhsnet) 172 | self.nodeInfo(rhs)['nets'].append(rhsnet) 173 | 174 | 175 | # Utility function to dump relevant information of the emulation 176 | def dump(): 177 | # Json dump of the topology 178 | with open(TOPOLOGY_FILE, 'w') as outfile: 179 | # Get json topology 180 | json_topology = json_graph.node_link_data(topology) 181 | # Convert links 182 | json_topology['links'] = [ 183 | { 184 | 'source': json_topology['nodes'][link['source']]['id'], 185 | 'target': json_topology['nodes'][link['target']]['id'], 186 | 'lhs_intf': link['lhs_intf'], 187 | 'rhs_intf': link['rhs_intf'], 188 | 'lhs_ip': str((ipaddress.ip_interface(link['lhs_ip'])).ip), 189 | 'rhs_ip': str((ipaddress.ip_interface(link['rhs_ip'])).ip) 190 | } 191 | for link in json_topology['links']] 192 | # Dump the topology 193 | json.dump(json_topology, outfile, sort_keys = True, indent = 2) 194 | # Dump for nodes.sh 195 | with open(NODES_SH, 'w') as outfile: 196 | # Create header 197 | nodes = "declare -a NODES=(" 198 | # Iterate over management ips 199 | for node, ip in nodes_to_mgmt.iteritems(): 200 | # Add the nodes one by one 201 | nodes = nodes + "%s " % ip 202 | if nodes_to_mgmt != {}: 203 | # Eliminate last character 204 | nodes = nodes[:-1] + ")\n" 205 | else: 206 | nodes = nodes + ")\n" 207 | # Write on the file 208 | outfile.write(nodes) 209 | 210 | # Utility function to shutdown the emulation 211 | def stopAll(): 212 | # Clean Mininet emulation environment 213 | os.system('sudo mn -c') 214 | # Kill all the started daemons 215 | os.system('sudo killall sshd zebra ospf6d') 216 | # Restart root ssh daemon 217 | os.system('service sshd restart') 218 | 219 | # Utility function to deploy Mininet topology 220 | def deploy( options ): 221 | # Retrieves options 222 | controller = options.controller 223 | topologyFile = options.topology 224 | clean_all = options.clean_all 225 | no_cli = options.no_cli 226 | # Clean all - clean and exit 227 | if clean_all: 228 | stopAll() 229 | return 230 | # Set Mininet log level to info 231 | setLogLevel('info') 232 | # Create Mininet topology 233 | topo = SRv6Topo(topo=topologyFile) 234 | # Create Mininet net 235 | net = Mininet(topo=topo, link=TCLink, 236 | build=False, controller=None) 237 | # Add manually external controller 238 | net.addController("c0", controller=RemoteController, ip=controller) 239 | # Build topology 240 | net.build() 241 | # Start topology 242 | net.start() 243 | # dump information 244 | dump() 245 | # Show Mininet prompt 246 | if not no_cli: 247 | # Mininet CLI 248 | CLI(net) 249 | # Stop topology 250 | net.stop() 251 | # Clean all 252 | stopAll() 253 | 254 | # Parse command line options and dump results 255 | def parseOptions(): 256 | parser = OptionParser() 257 | # IP of RYU controller 258 | parser.add_option('--controller', dest='controller', type='string', default="127.0.0.1", 259 | help='IP address of the Controlle instance') 260 | # Topology json file 261 | parser.add_option('--topology', dest='topology', type='string', default="example_srv6_topology.json", 262 | help='Topology file') 263 | # Clean all useful for rdcl stop action 264 | parser.add_option('--stop-all', dest='clean_all',action='store_true', help='Clean all mininet environment') 265 | # Start without Mininet prompt - useful for rdcl start action 266 | parser.add_option('--no-cli', dest='no_cli',action='store_true', help='Do not show Mininet CLI') 267 | # Parse input parameters 268 | (options, args) = parser.parse_args() 269 | # Done, return 270 | return options 271 | 272 | if __name__ == '__main__': 273 | # Let's parse input parameters 274 | opts = parseOptions() 275 | # Deploy topology 276 | deploy(opts) 277 | -------------------------------------------------------------------------------- /srv6_net_utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | ############################################################################################## 4 | # Copyright (C) 2018 Pier Luigi Ventre - (CNIT and University of Rome "Tor Vergata") 5 | # Copyright (C) 2018 Stefano Salsano - (CNIT and University of Rome "Tor Vergata") 6 | # www.uniroma2.it/netgroup - www.cnit.it 7 | # 8 | # 9 | # Licensed under the Apache License, Version 2.0 (the "License"); 10 | # you may not use this file except in compliance with the License. 11 | # You may obtain a copy of the License at 12 | # 13 | # http://www.apache.org/licenses/LICENSE-2.0 14 | # 15 | # Unless required by applicable law or agreed to in writing, software 16 | # distributed under the License is distributed on an "AS IS" BASIS, 17 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | # See the License for the specific language governing permissions and 19 | # limitations under the License. 20 | # 21 | # Net utils for Segment Routing IPv6 22 | # 23 | # @author Pier Luigi Ventre 24 | # @author Stefano Salsano 25 | 26 | from ipaddress import IPv6Network, IPv4Network 27 | 28 | # Allocates mgmt address 29 | class MgmtAllocator(object): 30 | 31 | bit = 64 32 | net = unicode("2000::/%d" % bit) 33 | prefix = 64 34 | 35 | def __init__(self): 36 | print "*** Calculating Available Mgmt Addresses" 37 | self.mgmtnet = (IPv6Network(self.net)).hosts() 38 | 39 | def nextMgmtAddress(self): 40 | n_host = next(self.mgmtnet) 41 | return n_host.__str__() -------------------------------------------------------------------------------- /srv6_utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | ############################################################################################## 4 | # Copyright (C) 2018 Pier Luigi Ventre - (CNIT and University of Rome "Tor Vergata") 5 | # Copyright (C) 2018 Stefano Salsano - (CNIT and University of Rome "Tor Vergata") 6 | # www.uniroma2.it/netgroup - www.cnit.it 7 | # 8 | # 9 | # Licensed under the Apache License, Version 2.0 (the "License"); 10 | # you may not use this file except in compliance with the License. 11 | # You may obtain a copy of the License at 12 | # 13 | # http://www.apache.org/licenses/LICENSE-2.0 14 | # 15 | # Unless required by applicable law or agreed to in writing, software 16 | # distributed under the License is distributed on an "AS IS" BASIS, 17 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | # See the License for the specific language governing permissions and 19 | # limitations under the License. 20 | # 21 | # Utils for Segment Routing IPv6 22 | # 23 | # @author Pier Luigi Ventre 24 | # @author Stefano Salsano 25 | 26 | from srv6_generators import * 27 | 28 | # Mininet 29 | from mininet.node import Host 30 | # General imports 31 | import re 32 | import os 33 | import shutil 34 | 35 | # Abstraction to model a SRv6Router 36 | class SRv6Router(Host): 37 | 38 | def __init__(self, name, *args, **kwargs): 39 | dirs = ['/var/mininet'] 40 | Host.__init__(self, name, privateDirs=dirs, *args, **kwargs) 41 | self.dir = "/tmp/%s" % name 42 | self.nets = [] 43 | if not os.path.exists(self.dir): 44 | os.makedirs(self.dir) 45 | 46 | # Config hook 47 | def config(self, **kwargs): 48 | # Init steps 49 | Host.config(self, **kwargs) 50 | # Iterate over the interfaces 51 | first = True 52 | for intf in self.intfs.itervalues(): 53 | # Remove any configured address 54 | self.cmd('ifconfig %s 0' %intf.name) 55 | # For the first one, let's configure the mgmt address 56 | if first: 57 | first = False 58 | self.cmd('ip a a %s dev %s' %(kwargs['mgmtip'], intf.name)) 59 | #let's write the hostname in /var/mininet/hostname 60 | self.cmd("echo '" + self.name + "' > /var/mininet/hostname") 61 | # Retrieve nets 62 | if kwargs.get('nets', None): 63 | self.nets = kwargs['nets'] 64 | # If requested 65 | if kwargs['sshd']: 66 | # Let's start sshd daemon in the hosts 67 | self.cmd('/usr/sbin/sshd -D &') 68 | # Configure the loopback address 69 | if kwargs.get('loopbackip', None): 70 | self.cmd('ip a a %s dev lo' %(kwargs['loopbackip'])) 71 | self.nets.append({'intf':'lo', 'ip':kwargs['loopbackip'], 'net':kwargs['loopbackip']}) 72 | # Enable IPv6 forwarding 73 | self.cmd("sysctl -w net.ipv6.conf.all.forwarding=1") 74 | # Enable SRv6 on the interface 75 | self.cmd("sysctl -w net.ipv6.conf.all.seg6_enabled=1") 76 | # Disable RA accept 77 | self.cmd("sysctl -w net.ipv6.conf.all.accept_ra=0") 78 | # Iterate over the interfaces 79 | for intf in self.intfs.itervalues(): 80 | # Enable IPv6 forwarding 81 | self.cmd("sysctl -w net.ipv6.conf.%s.forwarding=1" %intf.name) 82 | # Enable SRv6 on the interface 83 | self.cmd("sysctl -w net.ipv6.conf.%s.seg6_enabled=1" %intf.name) 84 | # Zebra and Quagga config 85 | if len(self.nets) > 0: 86 | zebra = open("%s/zebra.conf" % self.dir, 'w') 87 | ospfd = open("%s/ospf6d.conf" % self.dir, 'w') 88 | ospfd.write("! -*- ospf6 -*-\n!\nhostname %s\n" %self.name) 89 | ospfd.write("password srv6\nlog file %s/ospf6d.log\n!\n" %self.dir) 90 | zebra.write("! -*- zebra -*-\n!\nhostname %s\n" %self.name) 91 | zebra.write("password srv6\nenable password srv6\nlog file %s/zebra.log\n!\n" %self.dir) 92 | # Iterate over the nets and build interface part of the configs 93 | for net in self.nets: 94 | cost = 1 95 | ra_interval = 10 96 | # To mitigate annoying warnings 97 | if net['intf'] == 'lo': 98 | ospfd.write("interface %s\n!ipv6 ospf6 cost %s\nipv6 ospf6 hello-interval %s\n!\n" 99 | %(net['intf'], cost, 600)) 100 | else: 101 | ospfd.write("interface %s\nipv6 ospf6 cost %s\nipv6 ospf6 hello-interval %s\n!\n" 102 | %(net['intf'], cost, 1)) 103 | zebra.write("interface %s\nlink-detect\nno ipv6 nd suppress-ra\nipv6 nd ra-interval %s\nipv6 address %s\nipv6 nd prefix %s\n!\n" 104 | %(net['intf'], ra_interval, net['ip'], net['net'])) 105 | # Finishing ospf6d conf 106 | if kwargs.get('routerid', None): 107 | routerid = kwargs['routerid'] 108 | ospfd.write("router ospf6\nrouter-id %s\nredistribute static\n!\n" %routerid) 109 | ospfd.write("area 0.0.0.0 range %s\n" %RANGE_FOR_AREA_0) 110 | #Iterate again over the nets to finish area part 111 | for net in self.nets: 112 | ospfd.write("interface %s area 0.0.0.0\n" %(net['intf'])) 113 | ospfd.write("!\n") 114 | ospfd.close() 115 | zebra.close() 116 | # Right permission and owners 117 | self.cmd("chown quagga.quaggavty %s/*.conf" %self.dir) 118 | self.cmd("chown quagga.quaggavty %s/." %self.dir) 119 | self.cmd("chmod 640 %s/*.conf" %self.dir) 120 | # Starting daemons 121 | self.cmd("zebra -f %s/zebra.conf -d -z %s/zebra.sock -i %s/zebra.pid" %(self.dir, self.dir, self.dir)) 122 | self.cmd("ospf6d -f %s/ospf6d.conf -d -z %s/zebra.sock -i %s/ospf6d.pid" %(self.dir, self.dir, self.dir)) 123 | 124 | # Clean up the environment 125 | def cleanup(self): 126 | Host.cleanup(self) 127 | # Rm dir 128 | if os.path.exists(self.dir): 129 | shutil.rmtree(self.dir) 130 | 131 | 132 | -------------------------------------------------------------------------------- /topo/example_srv6_topology.json: -------------------------------------------------------------------------------- 1 | { 2 | "edges":[ 3 | { 4 | "source":"ads1", 5 | "target":"ads2", 6 | "view": "Data", 7 | "info": { 8 | "property": { 9 | "bw": 1, 10 | "delay": 1000 11 | }, 12 | "group": "" 13 | } 14 | }, 15 | { 16 | "source":"ads1", 17 | "target":"sur1", 18 | "view": "Data", 19 | "info": { 20 | "property": { 21 | "bw": 5, 22 | "delay": 200 23 | }, 24 | "group": "" 25 | } 26 | }, 27 | { 28 | "source":"ads2", 29 | "target":"sur1", 30 | "view": "Data", 31 | "info": { 32 | "property": { 33 | "bw": 10, 34 | "delay": 100 35 | }, 36 | "group": "" 37 | } 38 | } 39 | ], 40 | "vertices":[ 41 | { 42 | "info": { 43 | "type": "Router", 44 | "property": {}, 45 | "group": [200] 46 | }, 47 | "id": "ads1" 48 | }, 49 | { 50 | "info": { 51 | "type": "Router", 52 | "property": {}, 53 | "group": [200] 54 | }, 55 | "id": "ads2" 56 | }, 57 | { 58 | "info": { 59 | "type": "Router", 60 | "property": {}, 61 | "group": [200] 62 | }, 63 | "id": "sur1" 64 | } 65 | ], 66 | "graph_parameters":{ 67 | "testbed":"MININET" 68 | } 69 | } --------------------------------------------------------------------------------