def stop_bmx6_instances_all(remotes): for remote in remotes: exec(remote, f'pkill -SIGKILL -x bmx6 || true') exec(remote, f'rm -rf /tmp/bmx6-*')
def stop_bmx6_instances(ids, rmap): for id in ids: remote = rmap[id] exec(remote, f'pkill -SIGKILL -x bmx6 --nslist ns-{id} || true') exec(remote, f'rm -rf /tmp/bmx6_{id}')
def interface_down(remote, id, interface, ignore_error=False): exec(remote, f'ip netns exec "ns-{id}" ip link set "{interface}" down', ignore_error=ignore_error)
def interface_flush(remote, id, interface): # we need to flush for IPv4 and IPv6 exec(remote, f'ip netns exec "ns-{id}" ip -4 addr flush dev "{interface}"') exec(remote, f'ip netns exec "ns-{id}" ip -6 addr flush dev "{interface}"')
def stop_cjdns_instances_all(remotes): for remote in remotes: exec(remote, f'pkill -SIGKILL -x cjdroute || true') exec(remote, f'rm -f /tmp/cjdns-*.conf')
def stop_olsr2_instances(ids, rmap): for id in ids: remote = rmap[id] exec(remote, f'pkill -SIGKILL -x olsrd2 --nslist ns-{id} || true') exec(remote, f'rm -f /tmp/olsrd2-{id}.conf')
def stop_olsr1_instances_all(remotes): for remote in remotes: exec(remote, f'pkill -SIGKILL -x olsrd || true')
def l2tp_tunnel_exists(remote, tunnel_id): return int( exec(remote, f'ip l2tp show tunnel | grep -c "Tunnel {tunnel_id}" || true', get_output=True)[0]) != 0
def stop_babel_instances_all(remotes): for remote in remotes: exec(remote, f'pkill -SIGKILL -x babeld || true') exec(remote, f'rm -f /tmp/babel-*.pid')
def stop_babel_instances(ids, rmap): for id in ids: remote = rmap[id] exec(remote, f'pkill -SIGKILL -x babeld --nslist ns-{id} || true') exec(remote, f'rm -f /tmp/babel-{id}.pid')
def stop_yggdrasil_instances(ids, rmap): for id in ids: remote = rmap[id] exec(remote, f'pkill -SIGKILL -x yggdrasil --nslist ns-{id} || true') exec(remote, f'rm -f /tmp/yggdrasil-{id}.conf')
def stop_yggdrasil_instances_all(remotes): for remote in remotes: exec(remote, f'pkill -SIGKILL -x yggdrasil || true') exec(remote, f'rm -f /tmp/yggdrasil-*.conf')
def stop_cjdns_instances(ids, rmap): for id in ids: remote = rmap[id] exec(remote, f'pkill -SIGKILL -x cjdroute --nslist ns-{id} || true') exec(remote, f'rm -f /tmp/cjdns-{id}.conf')
def create_node(node, node_command=None, rmap={}): name = str(node['id']) remote = rmap.get(name) nsname = f'ns-{name}' brname = f'br-{name}' upname = 'uplink' downname = f'dl-{name}' exec(remote, f'ip netns add "{nsname}"') # up localhost exec(remote, f'ip netns exec "{nsname}" ip link set dev "lo" up') # create bridge exec(remote, f'ip netns exec "switch" ip link add name "{brname}" type bridge') configure_interface(remote, "switch", brname) # Disable spanning tree protocol (should be off by default anyway) exec( remote, f'ip netns exec "switch" ip link set "{brname}" type bridge stp_state 0' ) # Make the bridge to act as a hub exec( remote, f'ip netns exec "switch" ip link set "{brname}" type bridge ageing_time 0' ) exec( remote, f'ip netns exec "switch" ip link set "{brname}" type bridge forward_delay 0' ) # create interface pair in switch namespace exec( remote, f'ip netns exec "switch" ip link add name "{upname}" type veth peer name "{downname}"' ) # move uplink from namespace 'switch' into the nodes namespace exec(remote, f'ip netns exec "switch" ip link set "{upname}" netns "{nsname}"') # put uplinkport into bridge exec(remote, f'ip netns exec "switch" ip link set "{downname}" master "{brname}"') configure_interface(remote, 'switch', downname) configure_interface(remote, nsname, upname) if node_command is not None: exec( remote, f'ip netns exec "ns-{name}" {format_node_command(node_command, node)}' )
def stop_olsr1_instances(ids, rmap): for id in ids: remote = rmap[id] exec(remote, f'pkill -SIGKILL -x olsrd --nslist ns-{id} || true')
def l2tp_session_count(remote, tunnel_id): return int( exec(remote, f'ip l2tp show session | grep -c "tunnel {tunnel_id}" || true', get_output=True)[0])
def stop_olsr2_instances_all(remotes): for remote in remotes: exec(remote, f'pkill -SIGKILL -x olsrd2 || true') exec(remote, f'rm -f /tmp/olsrd2-*.conf')
def create_link(link, link_command=None, rmap={}): source = str(link['source']) target = str(link['target']) remote1 = rmap.get(source) remote2 = rmap.get(target) ifname1 = f've-{source}-{target}' ifname2 = f've-{target}-{source}' brname1 = f'br-{source}' brname2 = f'br-{target}' if source == target: eprint( f'Warning: Cannot create link with identical source ({source}) and target ({target}) => ignore' ) return if remote1 == remote2: # create veth interface pair exec( remote1, f'ip netns exec "switch" ip link add "{ifname1}" type veth peer name "{ifname2}"' ) else: # create l2tp connection addr1 = remote1.address addr2 = remote2.address # ids and port do not have to be the same on both remotes - but it is simpler that way tunnel_id = link_num(addr1, addr2, min=1, max=2**32) session_id = link_num(source, target, min=1, max=2**32) port = link_num(addr1, addr2, min=1024, max=2**16) if not l2tp_tunnel_exists(remote1, tunnel_id): exec( remote1, f'ip l2tp add tunnel tunnel_id {tunnel_id} peer_tunnel_id {tunnel_id} encap udp local {addr1} remote {addr2} udp_sport {port} udp_dport {port}' ) exec( remote1, f'ip l2tp add session name {ifname1} tunnel_id {tunnel_id} session_id {session_id} peer_session_id {session_id}' ) exec(remote1, f'ip link set "{ifname1}" netns "switch"') if not l2tp_tunnel_exists(remote2, tunnel_id): exec( remote2, f'ip l2tp add tunnel tunnel_id {tunnel_id} peer_tunnel_id {tunnel_id} encap udp local {addr2} remote {addr1} udp_sport {port} udp_dport {port}' ) exec( remote2, f'ip l2tp add session name {ifname2} tunnel_id {tunnel_id} session_id {session_id} peer_session_id {session_id}' ) exec(remote2, f'ip link set "{ifname2}" netns "switch"') configure_interface(remote1, 'switch', ifname1) configure_interface(remote2, 'switch', ifname2) # put into bridge exec(remote1, f'ip netns exec "switch" ip link set "{ifname1}" master "{brname1}"') exec(remote2, f'ip netns exec "switch" ip link set "{ifname2}" master "{brname2}"') # isolate interfaces (they can only speak to the downlink interface in the bridge they are) exec( remote1, f'ip netns exec "switch" bridge link set dev "{ifname1}" isolated on') exec( remote2, f'ip netns exec "switch" bridge link set dev "{ifname2}" isolated on') # e.g. execute tc command on link if link_command is not None: # source -> target exec( remote1, 'ip netns exec "switch" ' + format_link_command(link_command, link, 'source', ifname1)) # target -> source exec( remote2, 'ip netns exec "switch" ' + format_link_command(link_command, link, 'target', ifname2))
def apply(state={}, node_command=None, link_command=None, remotes=default_remotes): check_access(remotes) new_state = state (cur_state, cur_state_rmap) = get_current_state(remotes) # handle different new_state types if isinstance(new_state, str): if new_state == 'none': new_state = {} else: with open(new_state) as file: new_state = json.load(file) # map each node to a remote or local computer # distribute evenly with minimized interconnects rmap = _get_remote_mapping(cur_state, new_state, remotes, cur_state_rmap) data = _get_task(cur_state, new_state) beg_ms = millis() # add "switch" namespace if state_empty(cur_state): for remote in remotes: # add switch if it does not exist yet exec(remote, 'ip netns add "switch" || true') # disable IPv6 in switch namespace (no need, less overhead) exec( remote, 'ip netns exec "switch" sysctl -q -w net.ipv6.conf.all.disable_ipv6=1' ) for node in data.nodes_update: update_node(node, node_command, rmap) for link in data.links_update: update_link(link, link_command, rmap) for node in data.nodes_create: create_node(node, node_command, rmap) for link in data.links_create: create_link(link, link_command, rmap) for link in data.links_remove: remove_link(link, rmap) for node in data.nodes_remove: remove_node(node, rmap) # remove "switch" namespace if state_empty(new_state): if verbosity == 'normal': print(' remove "switch"') for remote in remotes: exec(remote, 'ip netns del "switch" || true') # wait for tasks to complete wait_for_completion() end_ms = millis() if verbosity != 'quiet': print('Network setup in {}:'.format(format_duration(end_ms - beg_ms))) print( f' nodes: {len(data.nodes_create)} created, {len(data.nodes_remove)} removed, {len(data.nodes_update)} updated' ) print( f' links: {len(data.links_create)} created, {len(data.links_remove)} removed, {len(data.links_update)} updated' ) return new_state