2 # NEPI, a framework to manage network experiments
3 # Copyright (C) 2013 INRIA
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation, either version 3 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program. If not, see <http://www.gnu.org/licenses/>.
18 # Author: Alina Quereilhac <alina.quereilhac@inria.fr>
33 ## - AQ: Add support for hypergraphs (to be able to add hyper edges to
34 ## model CSMA or wireless networks)
36 class NetGraph(object):
37 """ NetGraph represents a network topology.
38 Network graphs are internally using the networkx library.
42 def __init__(self, *args, **kwargs):
43 """ A graph can be generated using a specified pattern
44 (LADDER, MESH, TREE, etc), or provided as an argument.
46 :param graph: Undirected graph to use as internal representation
47 :type graph: networkx.Graph
49 :param topo_type: One of TopologyType.{LINEAR,LADDER,MESH,TREE,STAR}
50 used to automatically generate the topology graph.
51 :type topo_type: TopologyType
53 :param node_count: Number of nodes in the topology to be generated.
56 :param branches: Number of branches (arms) for the STAR topology.
60 :param assign_ips: Automatically assign IP addresses to each node.
61 :type assign_ips: bool
63 :param network: Base network segment for IP address assignment.
66 :param prefix: Base network prefix for IP address assignment.
69 :param version: IP version for IP address assignment.
73 :param assign_st: Select source and target nodes on the graph.
76 NOTE: Only point-to-point like network topologies are supported for now.
77 (Wireless and Ethernet networks were several nodes share the same
78 edge (hyperedge) can not be modeled for the moment).
81 self._graph = kwargs.get("graph")
82 self._topo_type = TopologyType.ADHOC
84 if not self._graph and kwargs.get("topo_type") and \
85 kwargs.get("node_count"):
86 topo_type = kwargs["topo_type"]
87 node_count = kwargs["node_count"]
88 branches = kwargs.get("branches")
90 self._topo_type = topo_type
91 self._graph = self.generate_grap(topo_type, node_count,
94 if kwargs.get("assign_ips"):
95 network = kwargs.get("network", "10.0.0.0")
96 prefix = kwargs.get("prefix", 8)
97 version = kwargs.get("version", 4)
99 self.assign_p2p_ips(self, network = network, prefix = prefix,
102 if kwargs.get("assign_st"):
103 self.select_target_zero()
104 self.select_random_leaf_source()
112 return self._topo_type
116 return self.graph.order()
120 return self.graph.nodes()
124 return self.graph.edges()
126 def generate_graph(self, topo_type, node_count, branches = None):
127 if topo_type == LADDER:
128 total_nodes = node_count/2
129 graph = networkx.ladder_graph(total_nodes)
131 elif topo_type == LINEAR:
132 graph = networkx.path_graph(node_count)
134 elif topo_type == MESH:
135 graph = networkx.complete_graph(node_count)
137 elif topo_type == TREE:
138 h = math.log(node_count + 1)/math.log(2) - 1
139 graph = networkx.balanced_tree(2, h)
141 elif topo_type == STAR:
142 graph = networkx.Graph()
145 nodesinbranch = (node_count - 1)/ BRANCHES
148 for i in xrange(BRANCHES):
150 for n in xrange(1, nodesinbranch + 1):
152 graph.add_edge(prev, c)
156 # node ids are int, make them str
158 g.add_nodes_from(map(lambda nid: NODES[str(nid)],
160 g.add_edges_from(map(lambda t: (NODES[str(t[0])], NODES[str(t[1])]),
165 def add_node(self, nid):
168 if nid not in self.graph:
169 self.graph.add_node(nid)
171 def add_edge(self, nid1, nid2):
178 if nid1 not in self.graph[nid2]:
179 self.graph.add_edge(nid2, nid1)
181 # The weight of the edge is the delay of the link
182 self.graph.edge[nid1][nid2]["weight"] = None
183 # confidence interval of the mean RTT
184 self.graph.edge[nid1][nid2]["weight_ci"] = None
186 def assign_p2p_ips(self, network = "10.0.0.0", prefix = 8, version = 4):
187 """ Assign IP addresses to each end of each edge of the network graph,
188 computing all the point to point subnets and addresses in the network
191 :param network: Base network address used for subnetting.
194 :param prefix: Prefix for the base network address used for subnetting.
197 :param version: IP version (either 4 or 6).
201 if len(networkx.connected_components(self.graph)) > 1:
202 raise RuntimeError("Disconnected graph!!")
204 # Assign IP addresses to host
205 netblock = "%s/%d" % (network, prefix)
207 net = ipaddr.IPv4Network(netblock)
210 net = ipaddr.IPv6Network(netblock)
213 raise RuntimeError, "Invalid IP version %d" % version
215 sub_itr = net.iter_subnets(new_prefix = new_prefix)
217 for nid1, nid2 in self.graph.edges():
218 #### Compute subnets for each link
220 # get a subnet of base_add with prefix /30
221 subnet = sub_itr.next()
222 mask = subnet.netmask.exploded
223 network = subnet.network.exploded
224 prefixlen = subnet.prefixlen
226 # get host addresses in that subnet
227 i = subnet.iterhosts()
233 self.graph.edge[nid1][nid2]["net"] = dict()
234 self.graph.edge[nid1][nid2]["net"][nid1] = ip1
235 self.graph.edge[nid1][nid2]["net"][nid2] = ip2
236 self.graph.edge[nid1][nid2]["net"]["mask"] = mask
237 self.graph.edge[nid1][nid2]["net"]["network"] = mask
238 self.graph.edge[nid1][nid2]["net"]["prefix"] = prefixlen
240 def get_p2p_info(self, nid1, nid2):
241 net = self.graph.edge[nid1][nid2]["net"]
242 return ( net[nid1], net[nid2], net["mask"], net["network"],
245 def set_source(self, nid):
246 graph.node[nid]["source"] = True
248 def set_target(self, nid):
249 graph.node[nid]["target"] = True
252 """ Returns the nodes that are targets """
253 return [nid for nid in self.graph.nodes() \
254 if self.graph.node[nid].get("target")]
257 """ Returns the nodes that are sources """
258 return [nid for nid in self.graph.nodes() \
259 if self.graph.node[nid].get("sources")]
261 def select_target_zero(self):
262 """ Marks the node 0 as target
266 def select_random_leaf_source(self):
267 """ Marks a random leaf node as source.
270 # The ladder is a special case because is not symmetric.
271 if self.topo_type == TopologyType.LADDER:
272 total_nodes = self.order/2
273 leaf1 = str(total_nodes - 1)
274 leaf2 = str(nodes - 1)
275 leaves = [leaf1, leaf2]
276 source = leaves.pop(random.randint(0, len(leaves) - 1))
278 # options must not be already sources or targets
279 options = [ k for k,v in graph.degree().iteritems() \
280 if v == 1 and not graph.node[k].get("source") \
281 and not graph.node[k].get("target")]
283 source = options.pop(random.randint(0, len(options) - 1))
285 self.set_source(source)