├── topos └── __init__.py ├── route.py ├── tools └── ntfl2topo.sh ├── addr.py ├── node.py └── net.py /topos/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /route.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | class Route(object): 4 | def __init__(self, dst, nh, cost): 5 | self.dst = dst 6 | self.nh = nh 7 | self.cost = cost 8 | 9 | def __hash__(self): 10 | return self.dst.__hash__() 11 | 12 | def __eq__(self, o): 13 | return self.dst == o.dst 14 | 15 | def __str__(self): 16 | return '%s via %s metric %d' % (self.dst, self.nh, self.cost) 17 | -------------------------------------------------------------------------------- /tools/ntfl2topo.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cat <= self.idx_high: 22 | if self.curnet[i] == 0xff: 23 | self.curnet[i] = 0 24 | i -= 1 25 | else: 26 | self.curnet[i] += 1 27 | break 28 | 29 | if i < self.idx_high: 30 | raise Exception("Out of nets !") 31 | 32 | return self.curnet 33 | -------------------------------------------------------------------------------- /node.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from route import * 4 | import copy, random 5 | 6 | def normalize(name): 7 | if len(name) > 12: 8 | return name[-12:] 9 | return name 10 | 11 | class Node(object): 12 | def __init__(self, name): 13 | self.name = name 14 | self.cur_intf = 0 15 | self.intfs = [] 16 | self.intfs_addr = {} 17 | self.addr = None 18 | 19 | self.routes = {} 20 | 21 | def add_route(self, r): 22 | if r.dst not in self.routes.keys(): 23 | self.routes[r.dst] = [r] 24 | else: 25 | self.routes[r.dst].append(r) 26 | 27 | def add_intf(self, intf): 28 | self.intfs.append(intf) 29 | 30 | def new_intf(self): 31 | i = self.cur_intf 32 | self.cur_intf += 1 33 | return i 34 | 35 | def get_portaddr(self, intf): 36 | return self.intfs_addr[intf].split("/")[0] 37 | 38 | def __hash__(self): 39 | return self.name.__hash__() 40 | 41 | def __eq__(self, o): 42 | return self.name == o.name 43 | 44 | class Edge(object): 45 | def __init__(self, node1, node2, port1, port2, cost, delay, bw): 46 | self.node1 = node1 47 | self.node2 = node2 48 | self.port1 = port1 49 | self.port2 = port2 50 | self.cost = cost 51 | self.delay = delay 52 | self.bw = bw 53 | 54 | class Topo(object): 55 | def __init__(self): 56 | self.nodes = set() 57 | self.edges = list() 58 | self.dmin = 0 59 | self.dmax = 0 60 | 61 | def copy(self): 62 | t = Topo() 63 | t.nodes = copy.deepcopy(self.nodes) 64 | t.edges = copy.deepcopy(self.edges) 65 | 66 | for e in t.edges: 67 | e.node1 = t.get_node(e.node1.name) 68 | e.node2 = t.get_node(e.node2.name) 69 | 70 | return t 71 | 72 | def copy_unit(self): 73 | t = self.copy() 74 | 75 | for e in t.edges: 76 | e.cost = 1 77 | 78 | t.compute() 79 | return t 80 | 81 | def build(self): 82 | pass 83 | 84 | def add_node(self, name): 85 | n = Node(normalize(name)) 86 | self.nodes.add(n) 87 | return n 88 | 89 | def get_node(self, name): 90 | for n in self.nodes: 91 | if n.name == normalize(name): 92 | return n 93 | 94 | return None 95 | 96 | def add_link(self, node1, node2, port1=None, port2=None, cost=1, delay=None, bw=None): 97 | if port1 is None: 98 | port1 = node1.new_intf() 99 | if port2 is None: 100 | port2 = node2.new_intf() 101 | 102 | node1.add_intf(port1) 103 | node2.add_intf(port2) 104 | 105 | if delay is None: 106 | delay = random.uniform(self.dmin, self.dmax) 107 | 108 | e = Edge(node1, node2, port1, port2, int(cost), delay, bw) 109 | self.edges.append(e) 110 | return e 111 | 112 | def add_link_name(self, name1, name2, *args, **kwargs): 113 | return self.add_link(self.get_node(name1), self.get_node(name2), *args, **kwargs) 114 | 115 | def get_edges(self, node1, node2): 116 | res = [] 117 | 118 | for e in self.edges: 119 | if e.node1 == node1 and e.node2 == node2 or e.node1 == node2 and e.node2 == node1: 120 | res.append(e) 121 | 122 | return res 123 | 124 | def get_minimal_edge_cost(self, edges): 125 | cost = 2**32 126 | for e in edges: 127 | if e.cost < cost: 128 | cost = e.cost 129 | return cost 130 | 131 | def get_all_minimal_edges(self, node1, node2): 132 | edges = self.get_edges(node1, node2) 133 | cost = self.get_minimal_edge_cost(edges) 134 | res = [] 135 | 136 | for e in edges: 137 | if e.cost == cost: 138 | res.append(e) 139 | 140 | return res 141 | 142 | def get_minimal_edge(self, node1, node2): 143 | edges = self.get_all_minimal_edges(node1, node2) 144 | 145 | if len(edges) == 0: 146 | return None 147 | 148 | return edges[0] 149 | 150 | def get_neighbors(self, node1): 151 | res = set() 152 | 153 | for e in self.edges: 154 | if e.node1 == node1: 155 | res.add(e.node2) 156 | elif e.node2 == node1: 157 | res.add(e.node1) 158 | 159 | return res 160 | 161 | def set_default_delay(self, dmin, dmax): 162 | self.dmin = dmin 163 | self.dmax = dmax 164 | 165 | # def get_min_neighbors(self, n): 166 | # res = set() 167 | # 168 | # mcost = 2**32 169 | # neighs = self.get_neighbors(n) 170 | # 171 | # for neigh in neighs: 172 | # e = self.get_minimal_edge(n, neigh) 173 | # if e.cost < mcost: 174 | # mcost = e.cost 175 | # 176 | # for neigh in neighs: 177 | # e = self.get_minimal_edge(n, neigh) 178 | # if e.cost == mcost: 179 | # 180 | 181 | def get_paths(self, Q, S, prev, u): 182 | w = prev[u] 183 | 184 | if w is None: 185 | Q.append(S) 186 | return 187 | 188 | S.append(u) 189 | 190 | for p in w: 191 | self.get_paths(Q, S[:], prev, p) 192 | 193 | def dijkstra(self, src): 194 | dist = {} 195 | prev = {} 196 | path = {} 197 | Q = set() 198 | 199 | dist[src] = 0 200 | prev[src] = None 201 | 202 | for v in self.nodes: 203 | if v != src: 204 | dist[v] = 2**32 205 | prev[v] = [] 206 | path[v] = [] 207 | Q.add(v) 208 | 209 | while len(Q) > 0: 210 | u = None 211 | tmpcost = 2**32 212 | for v in Q: 213 | if dist[v] < tmpcost: 214 | tmpcost = dist[v] 215 | u = v 216 | 217 | S = [] 218 | path[u] = [] 219 | 220 | self.get_paths(S, [], prev, u) 221 | for p in S: 222 | path[u].append(list(reversed(p))) 223 | 224 | Q.remove(u) 225 | 226 | neighs = self.get_neighbors(u) 227 | for v in neighs: 228 | if v not in Q: 229 | continue 230 | alt = dist[u] + self.get_minimal_edge(u, v).cost 231 | if alt < dist[v]: 232 | dist[v] = alt 233 | prev[v] = [u] 234 | elif alt == dist[v]: 235 | prev[v].append(u) 236 | 237 | return dist, path 238 | 239 | def get_port(self, n, e): 240 | if e.node1 == n: 241 | return e.port1 242 | if e.node2 == n: 243 | return e.port2 244 | return None 245 | 246 | def get_nh_from_paths(self, paths): 247 | nh = [] 248 | for p in paths: 249 | if len(p) == 0: 250 | continue 251 | if p[0] not in nh: 252 | nh.append(p[0]) 253 | return nh 254 | 255 | def compute_node(self, n): 256 | n.routes = {} 257 | dist, path = self.dijkstra(n) 258 | for t in dist.keys(): 259 | if len(path[t]) == 0: 260 | continue 261 | nh = self.get_nh_from_paths(path[t]) 262 | for p in nh: 263 | e = self.get_minimal_edge(n, p) 264 | tmp = self.get_port(p, e) 265 | r = Route(t.addr, p.get_portaddr(tmp), dist[t]) 266 | n.add_route(r) 267 | 268 | def compute(self): 269 | cnt = 0 270 | for n in self.nodes: 271 | print '# Running dijkstra for node %s (%d/%d)' % (n.name, cnt+1, len(self.nodes)) 272 | self.compute_node(n) 273 | cnt += 1 274 | -------------------------------------------------------------------------------- /net.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from addr import * 4 | from route import * 5 | import socket, os, sys 6 | import pickle 7 | 8 | class Nanonet(object): 9 | def __init__(self, topo, linknet=None, loopnet=None): 10 | self.topo = topo 11 | self.orig_topo = topo 12 | 13 | if linknet is None: 14 | linknet = V6Net('fc00:42::', 32, 64) 15 | 16 | if loopnet is None: 17 | loopnet = V6Net('fc00:2::', 32, 64) 18 | 19 | self.linknet = linknet 20 | self.loopnet = loopnet 21 | 22 | @staticmethod 23 | def load(fname): 24 | f = open(fname, 'r') 25 | obj = pickle.load(f) 26 | f.close() 27 | return obj 28 | 29 | def assign(self): 30 | for e in self.topo.edges: 31 | enet = self.linknet.next_net() 32 | a1 = enet[:] 33 | a2 = enet[:] 34 | a1[-1] = 1 35 | a2[-1] = 2 36 | 37 | # print 'Assigning %s - %s' % (socket.inet_ntop(socket.AF_INET6, str(a1)), socket.inet_ntop(socket.AF_INET6, str(a2))) 38 | # print 'With submask %d' % self.linknet.submask 39 | # print e.port1 40 | # print e.port2 41 | # print 'For port1 %d and port2 %d' % (e.port1, e.port2) 42 | 43 | e.node1.intfs_addr[e.port1] = socket.inet_ntop(socket.AF_INET6, str(a1))+'/'+str(self.linknet.submask) 44 | e.node2.intfs_addr[e.port2] = socket.inet_ntop(socket.AF_INET6, str(a2))+'/'+str(self.linknet.submask) 45 | 46 | for n in self.topo.nodes: 47 | enet = self.loopnet.next_net() 48 | enet[-1] = 1 49 | 50 | n.addr = socket.inet_ntop(socket.AF_INET6, str(enet))+'/'+str(self.loopnet.submask) 51 | 52 | def start(self, netname=None): 53 | print '# Building topology...' 54 | self.topo.build() 55 | print '# Assigning prefixes...' 56 | self.assign() 57 | 58 | print '# Running dijkstra... (%d nodes)' % len(self.topo.nodes) 59 | self.topo.compute() 60 | 61 | if netname is not None: 62 | f = open(netname, 'w') 63 | pickle.dump(self, f) 64 | f.close() 65 | 66 | def call(self, cmd): 67 | sys.stdout.write('%s\n' % cmd) 68 | 69 | def dump_commands(self, wr=(lambda x: self.call(x)), noroute=False): 70 | host_cmd = [] 71 | node_cmd = {} 72 | 73 | for n in self.topo.nodes: 74 | host_cmd.append('ip netns add %s' % n.name) 75 | node_cmd[n] = [] 76 | node_cmd[n].append('ifconfig lo up') 77 | node_cmd[n].append('ip -6 ad ad %s dev lo' % n.addr) 78 | node_cmd[n].append('sysctl net.ipv6.conf.all.forwarding=1') 79 | node_cmd[n].append('sysctl net.ipv6.conf.all.seg6_enabled=1') 80 | 81 | for e in self.topo.edges: 82 | dev1 = '%s-%d' % (e.node1.name, e.port1) 83 | dev2 = '%s-%d' % (e.node2.name, e.port2) 84 | 85 | host_cmd.append('ip link add name %s type veth peer name %s' % (dev1, dev2)) 86 | host_cmd.append('ip link set %s netns %s' % (dev1, e.node1.name)) 87 | host_cmd.append('ip link set %s netns %s' % (dev2, e.node2.name)) 88 | node_cmd[e.node1].append('ifconfig %s add %s up' % (dev1, e.node1.intfs_addr[e.port1])) 89 | node_cmd[e.node1].append('sysctl net.ipv6.conf.%s.seg6_enabled=1' % (dev1)) 90 | node_cmd[e.node2].append('ifconfig %s add %s up' % (dev2, e.node2.intfs_addr[e.port2])) 91 | node_cmd[e.node2].append('sysctl net.ipv6.conf.%s.seg6_enabled=1' % (dev2)) 92 | if e.delay > 0 and e.bw == 0: 93 | node_cmd[e.node1].append('tc qdisc add dev %s root handle 1: netem delay %.2fms' % (dev1, e.delay)) 94 | node_cmd[e.node2].append('tc qdisc add dev %s root handle 1: netem delay %.2fms' % (dev2, e.delay)) 95 | elif e.bw > 0: 96 | node_cmd[e.node1].append('tc qdisc add dev %s root handle 1: htb' % (dev1)) 97 | node_cmd[e.node1].append('tc class add dev %s parent 1: classid 1:1 htb rate %dkbit ceil %dkbit' % (dev1, e.bw, e.bw)) 98 | node_cmd[e.node1].append('tc filter add dev %s protocol ipv6 parent 1: prio 1 u32 match ip6 dst ::/0 flowid 1:1' % (dev1)) 99 | node_cmd[e.node2].append('tc qdisc add dev %s root handle 1: htb' % (dev2)) 100 | node_cmd[e.node2].append('tc class add dev %s parent 1: classid 1:1 htb rate %dkbit ceil %dkbit' % (dev2, e.bw, e.bw)) 101 | node_cmd[e.node2].append('tc filter add dev %s protocol ipv6 parent 1: prio 1 u32 match ip6 dst ::/0 flowid 1:1' % (dev2)) 102 | if e.delay > 0: 103 | node_cmd[e.node1].append('tc qdisc add dev %s parent 1:1 handle 10: netem delay %.2fms' % (dev1, e.delay)) 104 | node_cmd[e.node2].append('tc qdisc add dev %s parent 1:1 handle 10: netem delay %.2fms' % (dev2, e.delay)) 105 | 106 | if not noroute: 107 | for n in self.topo.nodes: 108 | for dst in n.routes.keys(): 109 | rts = n.routes[dst] 110 | laddr = n.addr.split('/')[0] 111 | if len(rts) == 1: 112 | r = rts[0] 113 | node_cmd[n].append('ip -6 ro ad %s via %s metric %d src %s' % (r.dst, r.nh, r.cost, laddr)) 114 | else: 115 | allnh = '' 116 | for r in rts: 117 | allnh += 'nexthop via %s weight 1 ' % (r.nh) 118 | node_cmd[n].append('ip -6 ro ad %s metric %d src %s %s' % (r.dst, r.cost, laddr, allnh)) 119 | 120 | for c in host_cmd: 121 | wr('%s' % c) 122 | 123 | for n in node_cmd.keys(): 124 | wr('ip netns exec %s bash -c \'%s\'' % (n.name, "; ".join(node_cmd[n]))) 125 | 126 | def igp_prepare_link_down(self, name1, name2): 127 | t = self.topo.copy() 128 | 129 | edge = t.get_minimal_edge(t.get_node(name1), t.get_node(name2)) 130 | t.edges.remove(edge) 131 | t.compute() 132 | 133 | rm_routes = {} 134 | chg_routes = {} 135 | for n in self.topo.nodes: 136 | n2 = t.get_node(n.name) 137 | rm_routes[n2] = [] 138 | chg_routes[n2] = [] 139 | 140 | for r in n.routes: 141 | if r not in n2.routes: 142 | rm_routes[n2].append(r) 143 | continue 144 | r2 = n2.routes[n2.routes.index(r)] 145 | if r.nh != r2.nh or r.cost != r2.cost: 146 | chg_routes[n2].append(r2) 147 | 148 | return (t, edge, rm_routes, chg_routes) 149 | 150 | # for n in rm_routes.keys(): 151 | # print '# Removed routes for node %s:' % n.name 152 | # for r in rm_routes[n]: 153 | # print '# %s via %s metric %d' % (r.dst, r.nh, r.cost) 154 | # for n in chg_routes.keys(): 155 | # print '# Changed routes for node %s:' % n.name 156 | # for r in chg_routes[n]: 157 | # print '# %s via %s metric %d' % (r.dst, r.nh, r.cost) 158 | 159 | def igp_apply_link_down(self, edge, rm_routes, chg_routes, timer=50): 160 | n1, n2 = edge.node1, edge.node2 161 | 162 | S = set() 163 | Q = set(self.topo.nodes) 164 | visited = set() 165 | S.add(n1) 166 | S.add(n2) 167 | 168 | # shut down interfaces 169 | self.call('ip netns exec %s ifconfig %s-%d down' % (n1.name, n1.name, edge.port1)) 170 | self.call('ip netns exec %s ifconfig %s-%d down' % (n2.name, n2.name, edge.port2)) 171 | 172 | while len(Q) > 0: 173 | S2 = set() 174 | self.call('sleep %f' % (timer/1000.0)) 175 | for n in S: 176 | for r in rm_routes[n]: 177 | self.call('ip netns exec %s ip -6 ro del %s' % (n.name, r.dst)) 178 | for r in chg_routes[n]: 179 | self.call('ip netns exec %s ip -6 ro replace %s via %s metric %d' % (n.name, r.dst, r.nh, r.cost)) 180 | visited.add(n) 181 | S2.update(self.topo.get_neighbors(n)) 182 | S2.difference_update(visited) 183 | Q.remove(n) 184 | S = S2 185 | 186 | def apply_topo(self, t): 187 | self.topo = t 188 | --------------------------------------------------------------------------------