示例#1
0
def main():
    parser = argparse.ArgumentParser(description='Measure mean traffic.')
    parser.add_argument(
        '--remotes',
        help='Distribute nodes and links on remotes described in the JSON file.'
    )
    parser.add_argument('--interface', help='Interface to measure traffic on.')
    parser.add_argument('--duration',
                        type=int,
                        help='Measurement duration in seconds.')

    args = parser.parse_args()

    if args.remotes:
        if not os.path.isfile(args.remotes):
            eprint(f'File not found: {args.remotes}')
            stop_all_terminals()
            exit(1)

        with open(args.remotes) as file:
            args.remotes = [Remote.from_json(obj) for obj in json.load(file)]
    else:
        args.remotes = default_remotes

    # need root for local setup
    for remote in args.remotes:
        if remote.address is None:
            if os.geteuid() != 0:
                eprint('Need to run as root.')
                exit(1)

    rmap = get_remote_mapping(args.remotes)
    if args.duration:
        ds = args.duration
        ts_beg = traffic(args.remotes, interface=args.interface, rmap=rmap)
        time.sleep(ds)
        ts_end = traffic(args.remotes, interface=args.interface, rmap=rmap)
        ts = ts_end - ts_beg
        n = ds * len(rmap)
        print(
            f'rx: {format_size(ts.rx_bytes / n)}/s, {ts.rx_packets / n:.2f} packets/s, {ts.rx_dropped / n:.2f} dropped/s (avg. per node)'
        )
        print(
            f'tx: {format_size(ts.tx_bytes / n)}/s, {ts.tx_packets / n:.2f} packets/s, {ts.tx_dropped / n:.2f} dropped/s (avg. per node)'
        )
    else:
        ts = traffic(args.remotes, interface=args.interface, rmap=rmap)
        print(
            f'rx: {format_size(ts.rx_bytes)} / {ts.rx_packets} packets / {ts.rx_dropped} dropped'
        )
        print(
            f'tx: {format_size(ts.tx_bytes)} / {ts.tx_packets} packets / {ts.tx_dropped} dropped'
        )

    stop_all_terminals()
示例#2
0
def start_routing_protocol(protocol, rmap, ids, ignore_error=False):
    beg_count = count_instances(protocol, rmap)
    beg_ms = millis()

    for id in ids:
        remote = rmap[id]
        interface_up(remote, id, 'uplink', ignore_error)

    if protocol == 'babel':
        start_babel_instances(ids, rmap)
    elif protocol == 'batman-adv':
        start_batmanadv_instances(ids, rmap)
    elif protocol == 'bmx6':
        start_bmx6_instances(ids, rmap)
    elif protocol == 'cjdns':
        start_cjdns_instances(ids, rmap)
    elif protocol == 'olsr1':
        start_olsr1_instances(ids, rmap)
    elif protocol == 'olsr2':
        start_olsr2_instances(ids, rmap)
    elif protocol == 'ospf':
        start_ospf_instances(ids, rmap)
    elif protocol == 'yggdrasil':
        start_yggdrasil_instances(ids, rmap)
    elif protocol == 'none':
        return
    else:
        eprint(f'Error: unknown routing protocol: {protocol}')
        exit(1)

    wait_for_completion()

    # wait for last started process to fork
    # otherwise we might have one extra counted instance
    time.sleep(0.5)

    end_ms = millis()
    end_count = count_instances(protocol, rmap)

    count = end_count - beg_count
    if count != len(ids):
        eprint(
            f'Error: Failed to start {protocol} instances: {count}/{len(ids)} started'
        )
        stop_all_terminals()
        exit(1)

    if verbosity != 'quiet':
        print('Started {} {} instances in {}'.format(
            count, protocol, format_duration(end_ms - beg_ms)))
示例#3
0
def _stop_protocol(protocol, rmap, ids):
    base = os.path.dirname(os.path.realpath(__file__))
    path = f'{base}/protocols/{protocol}_stop.sh'

    if not os.path.isfile(path):
        eprint(f'File does not exist: {path}')
        stop_all_terminals()
        exit(1)

    for id in ids:
        remote = rmap[id]

        label = remote.address or 'local'
        cmd = f'ip netns exec ns-{id} sh -s {label} {id} < {path}'

        _exec_verbose(remote, cmd)

    wait_for_completion()
示例#4
0
    def partition_into_subgraph_nodes(neighbor_map, nodes, rmap, remotes):
        random.shuffle(nodes)
        # remote_id => [<node_ids>]
        partitions = {}

        # keep running nodes on the same partition
        for node_id, remote in rmap.items():
            remote_id = remotes.index(remote)
            partitions.setdefault(remote_id, []).append(node_id)
            if node_id not in nodes:
                eprint(f'Node {node_id} not in previous state!')
                stop_all_terminals()
                exit(1)
            nodes.remove(node_id)

        for remote_id in range(len(remotes)):
            if len(nodes) == 0:
                break
            if remote_id not in partitions:
                partitions[remote_id] = [nodes.pop()]

        # find neighbor node of cluster
        def grow_cluster(cluster, nodes):
            for node in nodes:
                for cluster_node in cluster:
                    if node in neighbor_map[cluster_node]:
                        cluster.append(node)
                        nodes.remove(node)
                        return
            # cannot extend cluster via neighbor => use left over node
            cluster.append(nodes.pop())

        while len(nodes) > 0:
            # get smallest cluster (remote) key
            key = min(partitions.keys(), key=lambda k: len(partitions[k]))
            grow_cluster(partitions[key], nodes)

        return partitions
示例#5
0
def _get_random_paths(nodes,
                      count=10,
                      seed=None,
                      sample_without_replacement=False):
    if sample_without_replacement:
        if count > (len(nodes) / 2):
            eprint(
                f"Not enough nodes ({len(nodes)}) to generate {count} unique paths."
            )
            stop_all_terminals()
            exit(1)
    else:
        if len(nodes) < 2:
            eprint(
                f"Not enough nodes ({len(nodes)}) to generate {count} paths.")
            stop_all_terminals()
            exit(1)

    if seed is not None:
        random.seed(seed)

    paths = []
    s = list(range(0, len(nodes)))
    for i in range(count):
        a = random.choice(s[:-1])
        a_index = s.index(a)
        b = random.choice(s[(a_index + 1):])
        b_index = s.index(b)

        if sample_without_replacement:
            s = s[:a_index] + s[(a_index + 1):b_index] + s[(b_index + 1):]

        if random.uniform(0, 1) > 0.5:
            paths.append((nodes[a], nodes[b]))
        else:
            paths.append((nodes[b], nodes[a]))

    return paths
示例#6
0
def _get_random_paths(nodes, count=10, seed=None):
    if count > (len(nodes) * (len(nodes) - 1) // 2):
        eprint(f'Path count ({count}) too big to generate unique paths.')
        stop_all_terminals()
        exit(1)

    if seed is not None:
        random.seed(seed)

    def decode(items, i):
        k = math.floor((1 + math.sqrt(1 + 8 * i)) / 2)
        return (items[k], items[i - k * (k - 1) // 2])

    def rand_pair(n):
        return decode(random.randrange(n * (n - 1) // 2))

    def rand_pairs(items, npairs):
        n = len(items)
        return [
            decode(items, i)
            for i in random.sample(range(n * (n - 1) // 2), npairs)
        ]

    return rand_pairs(nodes, count)
示例#7
0
def _process_json(json_data):
    # in reality, only '@', ':', '/' and whitespace should cause problems
    name_re = re.compile(r'^[\w-]{1,6}$')
    links = {}
    nodes = {}

    for node in json_data.get('nodes', []):
        name = str(node['id'])
        if not name_re.match(name):
            eprint(f'Invalid node name: {name}')
            stop_all_terminals()
            exit(1)

        nodes[name] = node

    for link in json_data.get('links', []):
        source = str(link['source'])
        target = str(link['target'])

        if len(source) > 6:
            eprint(f'Node name too long: {source}')
            stop_all_terminals()
            exit(1)

        if len(target) > 6:
            eprint(f'Node name too long: {target}')
            stop_all_terminals()
            exit(1)

        if source not in nodes:
            nodes[source] = {'id': source}

        if target not in nodes:
            nodes[target] = {'id': target}

        if source > target:
            links[f'{source}-{target}'] = link
        else:
            links[f'{target}-{source}'] = link

    return (links, nodes)
示例#8
0
def _get_remote_mapping(cur_state, new_state, remotes, cur_state_rmap):
    def partition_into_subgraph_nodes(neighbor_map, nodes, rmap, remotes):
        random.shuffle(nodes)
        # remote_id => [<node_ids>]
        partitions = {}

        # keep running nodes on the same partition
        for node_id, remote in rmap.items():
            remote_id = remotes.index(remote)
            partitions.setdefault(remote_id, []).append(node_id)
            if node_id not in nodes:
                eprint(f'Node {node_id} not in previous state!')
                stop_all_terminals()
                exit(1)
            nodes.remove(node_id)

        for remote_id in range(len(remotes)):
            if len(nodes) == 0:
                break
            if remote_id not in partitions:
                partitions[remote_id] = [nodes.pop()]

        # find neighbor node of cluster
        def grow_cluster(cluster, nodes):
            for node in nodes:
                for cluster_node in cluster:
                    if node in neighbor_map[cluster_node]:
                        cluster.append(node)
                        nodes.remove(node)
                        return
            # cannot extend cluster via neighbor => use left over node
            cluster.append(nodes.pop())

        while len(nodes) > 0:
            # get smallest cluster (remote) key
            key = min(partitions.keys(), key=lambda k: len(partitions[k]))
            grow_cluster(partitions[key], nodes)

        return partitions

    # get node distribution balance
    def get_variance(partition):
        median = 0
        for remote_id, cluster in partition.items():
            median += len(cluster)
        median /= len(partition)

        q = 0
        for remote_id, cluster in partition.items():
            q = (len(cluster) - median)**2

        return math.sqrt(q / len(partition))

    def partition_to_map(partition, remotes):
        node_to_remote_map = {}
        for remote_id, node_ids in partition.items():
            for node_id in node_ids:
                node_to_remote_map[node_id] = remotes[remote_id]
        return node_to_remote_map

    '''
    # debug output
    def debug_partition(partition, remotes):
        print('partitioning:')

        for remote_id, cluster in partition.items():
            print('  {}: {} nodes'.format(remotes[remote_id].get('address', 'local'), len(cluster)))

        interconnects = 0
        node_to_remote_map = partition_to_map(partition, remotes)
        for link in new_state.get('links', []):
            if node_to_remote_map[str(link['source'])] is not node_to_remote_map[str(link['target'])]:
                interconnects += 1
        print(f'  l2tp links: {interconnects}')
    '''

    # try multiple random (connected) partitions and select the best
    neighbor_map = convert_to_neighbors(cur_state, new_state)
    tries = 20
    lowest_variance = math.inf
    best_partition = []

    # shortcut: if no mapping on multiple remotes is needed
    if len(remotes) == 1 and len(cur_state_rmap) == 0:
        return partition_to_map({0: neighbor_map.keys()}, remotes)

    for _ in range(tries):
        partition = partition_into_subgraph_nodes(neighbor_map,
                                                  list(neighbor_map.keys()),
                                                  cur_state_rmap, remotes)
        if partition:
            variance = get_variance(partition)
            if variance < lowest_variance:
                lowest_variance = variance
                best_partition = partition

    if len(best_partition) == 0:
        eprint('No network partition found.')
        stop_all_terminals()
        exit(1)

    #if verbosity in ['verbose', 'normal']:
    #    debug_partition(best_partition, remotes)

    # node_id => remote
    return partition_to_map(best_partition, remotes)
示例#9
0
                                  min_hops=2,
                                  path_count=link_count)
        ping_result = ping.ping(remotes=remotes,
                                paths=paths,
                                duration_ms=30000,
                                verbosity='verbose')

        sysload_result = shared.sysload(remotes)

        software.clear(remotes)

        # add data to csv file
        extra = (['node_count',
                  'software_startup_ms'], [node_count, software_startup_ms])
        shared.csv_update(csvfile, '\t', extra, ping_result.getData(),
                          sysload_result)

        network.clear(remotes)

        # abort benchmark when less then 40% of the pings arrive
        if ping_result.transmitted == 0 or (ping_result.received /
                                            ping_result.transmitted) < 0.4:
            break


for protocol in ['babel', 'batman-adv', 'yggdrasil']:
    with open(f"{prefix}benchmark1-{protocol}.csv", 'w+') as csvfile:
        run(protocol, csvfile)

shared.stop_all_terminals()
示例#10
0
def main():
    parser = argparse.ArgumentParser(description="Ping various nodes.")
    parser.add_argument(
        "--remotes",
        help=
        "Distribute nodes and links on remotes described in the JSON file.",
    )
    parser.add_argument("--input", help="JSON state of the network.")
    parser.add_argument("--interface",
                        help="Interface to send data over (autodetected).")
    parser.add_argument("--min-hops",
                        type=int,
                        help="Minimum hops to ping. Needs --input.")
    parser.add_argument("--max-hops",
                        type=int,
                        help="Maximum hops to ping. Needs --input.")
    parser.add_argument(
        "--pings",
        type=int,
        default=10,
        help="Number of pings (unique, no self, no reverse paths).",
    )
    parser.add_argument("--duration",
                        type=int,
                        default=1000,
                        help="Spread pings over duration in ms.")
    parser.add_argument(
        "--deadline",
        type=int,
        default=1,
        help=
        "Specify a timeout, in seconds, before ping exits regardless of how many packets have been sent or received. In this case ping does not stop after count packet are sent, it waits either for deadline expire or until count probes are answered or for some error notification from network.",
    )
    parser.add_argument(
        "--timeout",
        type=int,
        default=None,
        help=
        "Time to wait for a response, in seconds. The option affects only timeout in absence of any responses, otherwise ping waits for two RTTs.",
    )
    parser.add_argument("--path",
                        nargs=2,
                        help="Send pings from a node to another.")
    parser.add_argument("-4",
                        action="store_true",
                        help="Force use of IPv4 addresses.")
    parser.add_argument("-6",
                        action="store_true",
                        help="Force use of IPv6 addresses.")

    args = parser.parse_args()

    if args.remotes:
        if not os.path.isfile(args.remotes):
            eprint(f"File not found: {args.remotes}")
            stop_all_terminals()
            exit(1)

        with open(args.remotes) as file:
            args.remotes = [Remote.from_json(obj) for obj in json.load(file)]
    else:
        args.remotes = default_remotes

    # need root for local setup
    for remote in args.remotes:
        if remote.address is None:
            if os.geteuid() != 0:
                eprint("Need to run as root.")
                exit(1)

    paths = None

    if args.path:
        for ns in args.path:
            if not namespace_exists(args.remotes, ns):
                eprint(f"Namespace ns-{ns} does not exist")
                stop_all_terminals()
                exit(1)
        paths = [args.path]
    elif args.input:
        state = json.load(args.input)
        paths = get_random_paths(network=state, count=args.pings)
        paths = filter_paths(state,
                             paths,
                             min_hops=args.min_hops,
                             max_hops=args.max_hops)
    else:
        if args.min_hops is not None or args.max_hops is not None:
            eprint(
                "No min/max hops available without topology information (--input)"
            )
            stop_all_terminals()
            exit(1)

        rmap = get_remote_mapping(args.remotes)
        all = list(rmap.keys())
        paths = _get_random_paths(nodes=all, count=args.pings)

    address_type = None
    if getattr(args, "4"):
        address_type = "4"
    if getattr(args, "6"):
        address_type = "6"

    ping(
        paths=paths,
        remotes=args.remotes,
        duration_ms=args.duration,
        interface=args.interface,
        verbosity="verbose",
        address_type=address_type,
        ping_deadline=args.deadline,
        ping_timeout=args.timeout,
    )

    stop_all_terminals()
示例#11
0
def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('--verbosity',
                        choices=['verbose', 'normal', 'quiet'],
                        default='normal',
                        help='Set verbosity.')
    parser.add_argument(
        '--remotes',
        help='Distribute nodes and links on remotes described in the JSON file.'
    )
    parser.add_argument('--ip-protocol',
                        choices=['4', '6'],
                        help='Use IPv4/IPv6 only.')
    parser.set_defaults(to_state=None)

    subparsers = parser.add_subparsers(dest='action',
                                       required=True,
                                       help='Action')

    parser_start = subparsers.add_parser(
        'start', help='Start protocol daemons in every namespace.')
    parser_start.add_argument('protocol',
                              choices=protocol_choices,
                              help='Routing protocol to start.')
    parser_start.add_argument('to_state',
                              nargs='?',
                              default=None,
                              help='To state')

    parser_stop = subparsers.add_parser(
        'stop', help='Stop protocol daemons in every namespace.')
    parser_stop.add_argument('protocol',
                             choices=protocol_choices,
                             help='Routing protocol to stop.')
    parser_stop.add_argument('to_state',
                             nargs='?',
                             default=None,
                             help='To state')

    parser_change = subparsers.add_parser(
        'apply', help='Stop/Start protocol daemons in every namespace.')
    parser_change.add_argument('protocol',
                               choices=protocol_choices,
                               help='Routing protocol to change.')
    parser_change.add_argument('to_state',
                               nargs='?',
                               default=None,
                               help='To state')

    parser_run = subparsers.add_parser(
        'run', help='Execute any command in every namespace.')
    parser_run.add_argument(
        'command',
        nargs=argparse.REMAINDER,
        help='Shell command that is run. {name} is replaced by the nodes name.'
    )
    parser_run.add_argument('--quiet',
                            action='store_true',
                            help='Do not output stdout and stderr.')
    parser_run.add_argument('to_state',
                            nargs='?',
                            default=None,
                            help='To state')

    parser_clear = subparsers.add_parser('clear',
                                         help='Stop all routing protocols.')

    args = parser.parse_args()

    global ip_protocol
    ip_protocol = args.ip_protocol

    if args.remotes:
        with open(args.remotes) as file:
            args.remotes = json.load(file)
    else:
        args.remotes = default_remotes

    check_access(args.remotes)

    global verbosity
    verbosity = args.verbosity

    # get nodes that have been added or will be removed
    (old_ids, new_ids, rmap) = _get_update(args.to_state, args.remotes)

    if args.action == 'start':
        if args.to_state:
            start_routing_protocol(args.protocol, rmap, new_ids)
        else:
            all = list(rmap.keys())
            start_routing_protocol(args.protocol, rmap, all)
    elif args.action == 'stop':
        if args.to_state:
            stop_routing_protocol(args.protocol, rmap, old_ids)
        else:
            all = list(rmap.keys())
            stop_routing_protocol(args.protocol, rmap, all)
    elif args.action == 'apply':
        stop_routing_protocol(args.protocol, rmap, old_ids)
        start_routing_protocol(args.protocol, rmap, new_ids)
    elif args.action == 'clear':
        clear(args.remotes)
    elif args.action == 'run':
        run(' '.join(args.command), rmap, args.quiet)
    else:
        eprint('Unknown action: {}'.format(args.action))
        exit(1)

    stop_all_terminals()
示例#12
0
def main():
    parser = argparse.ArgumentParser()
    parser.add_argument(
        '--remotes',
        help='Distribute nodes and links on remotes described in the JSON file.'
    )
    subparsers = parser.add_subparsers(dest='action', required=True)

    parser_traffic = subparsers.add_parser('traffic',
                                           help='Measure mean traffic.')
    parser_traffic.add_argument('--interface',
                                help='Interface to measure traffic on.')
    parser_traffic.add_argument('--duration',
                                type=int,
                                help='Measurement duration in seconds.')

    parser_ping = subparsers.add_parser('ping', help='Ping various nodes.')
    parser_ping.add_argument('--input', help='JSON state of the network.')
    parser_ping.add_argument(
        '--interface', help='Interface to send data over (autodetected).')
    parser_ping.add_argument('--min-hops',
                             type=int,
                             help='Minimum hops to ping. Needs --input.')
    parser_ping.add_argument('--max-hops',
                             type=int,
                             help='Maximum hops to ping. Needs --input.')
    parser_ping.add_argument(
        '--pings',
        type=int,
        default=10,
        help='Number of pings (unique, no self, no reverse paths).')
    parser_ping.add_argument('--duration',
                             type=int,
                             default=1000,
                             help='Spread pings over duration in ms.')

    args = parser.parse_args()

    if args.remotes:
        with open(args.remotes) as file:
            args.remotes = json.load(file)
    else:
        args.remotes = default_remotes

    # need root for local setup
    for remote in args.remotes:
        if remote.get('address') is None:
            if os.geteuid() != 0:
                eprint('Need to run as root.')
                exit(1)

    if args.action == 'traffic':
        rmap = get_remote_mapping(args.remotes)
        if args.duration:
            ds = args.duration
            ts_beg = traffic(args.remotes, interface=args.interface, rmap=rmap)
            time.sleep(ds)
            ts_end = traffic(args.remotes, interface=args.interface, rmap=rmap)
            ts = ts_end - ts_beg
            n = ds * len(rmap)
            print(
                f'rx: {format_size(ts.rx_bytes / n)}/s, {ts.rx_packets / n:.2f} packets/s, {ts.rx_dropped / n:.2f} dropped/s (avg. per node)'
            )
            print(
                f'tx: {format_size(ts.tx_bytes / n)}/s, {ts.tx_packets / n:.2f} packets/s, {ts.tx_dropped / n:.2f} dropped/s (avg. per node)'
            )
        else:
            ts = traffic(args.remotes, interface=args.interface, rmap=rmap)
            print(
                f'rx: {format_size(ts.rx_bytes)} / {ts.rx_packets} packets / {ts.rx_dropped} dropped'
            )
            print(
                f'tx: {format_size(ts.tx_bytes)} / {ts.tx_packets} packets / {ts.tx_dropped} dropped'
            )
    elif args.action == 'ping':
        paths = None

        if args.input:
            state = json.load(args.input)
            paths = get_random_paths(network=state, count=args.pings)
            paths = filter_paths(state,
                                 paths,
                                 min_hops=args.min_hops,
                                 max_hops=args.max_hops)
        else:
            if args.min_hops is not None or args.max_hops is not None:
                eprint(
                    'No min/max hops available without topology information (--input)'
                )
                stop_all_terminals()
                exit(1)

            rmap = get_remote_mapping(args.remotes)
            all = list(rmap.keys())
            paths = _get_random_paths(nodes=all, count=args.pings)

        ping_paths(paths=paths,
                   remotes=args.remotes,
                   duration_ms=args.duration,
                   interface=args.interface,
                   verbosity='verbose')
    else:
        eprint(f'Unknown action: {args.action}')
        exit(1)

    stop_all_terminals()
示例#13
0
def main():
    parser = argparse.ArgumentParser(
        formatter_class=argparse.RawDescriptionHelpFormatter,
        description=
        'Create a virtual network based on linux network names and virtual network interfaces:\n ./network.py change none test.json'
    )

    parser.add_argument('--verbosity',
                        choices=['verbose', 'normal', 'quiet'],
                        default='normal',
                        help='Set verbosity.')
    parser.add_argument(
        '--link-command',
        help=
        'Execute a command to change link properties. JSON elements are accessible. E.g. "myscript.sh {ifname} {tq}"'
    )
    parser.add_argument(
        '--node-command',
        help=
        'Execute a command to change link properties. JSON elements are accessible. E.g. "myscript.sh {ifname} {id}"'
    )
    parser.add_argument('--block-arp',
                        action='store_true',
                        help='Block ARP packets.')
    parser.add_argument('--block-multicast',
                        action='store_true',
                        help='Block multicast packets.')
    parser.add_argument(
        '--remotes',
        help='Distribute nodes and links on remotes described in the JSON file.'
    )

    subparsers = parser.add_subparsers(dest='action', required=True)

    parser_change = subparsers.add_parser(
        'apply', help='Create or change a virtual network.')
    parser_change.add_argument(
        'new_state',
        help=
        'JSON file that describes the target topology. Use "none" to remove all network namespaces.'
    )
    subparsers.add_parser(
        'show',
        help=
        'List all Linux network namespaces. Namespace "switch" is the special cable cabinet namespace.'
    )
    subparsers.add_parser(
        'clear',
        help=
        'Remove all Linux network namespaces. Processes still might need to be killed.'
    )

    args = parser.parse_args()

    block_arp = args.block_arp
    block_multicast = args.block_multicast
    verbosity = args.verbosity

    if args.remotes:
        with open(args.remotes) as file:
            args.remotes = json.load(file)
    else:
        args.remotes = default_remotes

    if args.action == 'clear':
        clear(args.remotes)
    elif args.action == 'show':
        show(args.remotes)
    elif args.action == 'apply':
        apply(args.new_state, args.node_command, args.link_command,
              args.remotes)
    else:
        eprint(f'Invalid command: {args.action}')
        exit(1)
    stop_all_terminals()
示例#14
0
def main():
    parser = argparse.ArgumentParser(description='Ping various nodes.')
    parser.add_argument(
        '--remotes',
        help='Distribute nodes and links on remotes described in the JSON file.'
    )
    parser.add_argument('--input', help='JSON state of the network.')
    parser.add_argument('--interface',
                        help='Interface to send data over (autodetected).')
    parser.add_argument('--min-hops',
                        type=int,
                        help='Minimum hops to ping. Needs --input.')
    parser.add_argument('--max-hops',
                        type=int,
                        help='Maximum hops to ping. Needs --input.')
    parser.add_argument(
        '--pings',
        type=int,
        default=10,
        help='Number of pings (unique, no self, no reverse paths).')
    parser.add_argument('--duration',
                        type=int,
                        default=1000,
                        help='Spread pings over duration in ms.')
    parser.add_argument('-4',
                        action='store_true',
                        help='Force use of IPv4 addresses.')
    parser.add_argument('-6',
                        action='store_true',
                        help='Force use of IPv6 addresses.')

    args = parser.parse_args()

    if args.remotes:
        if not os.path.isfile(args.remotes):
            eprint(f'File not found: {args.remotes}')
            stop_all_terminals()
            exit(1)

        with open(args.remotes) as file:
            args.remotes = [Remote.from_json(obj) for obj in json.load(file)]
    else:
        args.remotes = default_remotes

    # need root for local setup
    for remote in args.remotes:
        if remote.address is None:
            if os.geteuid() != 0:
                eprint('Need to run as root.')
                exit(1)

    paths = None

    if args.input:
        state = json.load(args.input)
        paths = get_random_paths(network=state, count=args.pings)
        paths = filter_paths(state,
                             paths,
                             min_hops=args.min_hops,
                             max_hops=args.max_hops)
    else:
        if args.min_hops is not None or args.max_hops is not None:
            eprint(
                'No min/max hops available without topology information (--input)'
            )
            stop_all_terminals()
            exit(1)

        rmap = get_remote_mapping(args.remotes)
        all = list(rmap.keys())
        paths = _get_random_paths(nodes=all, count=args.pings)

    address_type = None
    if getattr(args, '4'):
        address_type = '4'
    if getattr(args, '6'):
        address_type = '6'

    ping(paths=paths,
         remotes=args.remotes,
         duration_ms=args.duration,
         interface=args.interface,
         verbosity='verbose',
         address_type=address_type)

    stop_all_terminals()
示例#15
0
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:
            if not os.path.isfile(new_state):
                eprint(f'File not found: {new_state}')
                stop_all_terminals()
                exit(1)

            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):
        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
示例#16
0
def main():
    parser = argparse.ArgumentParser(
        formatter_class=argparse.RawDescriptionHelpFormatter,
        description=
        'Create a virtual network based on linux network names and virtual network interfaces:\n ./network.py change none test.json'
    )

    parser.add_argument('--verbosity',
                        choices=['verbose', 'normal', 'quiet'],
                        default='normal',
                        help='Set verbosity.')
    parser.add_argument(
        '--link-command',
        help=
        'Execute a command to change link properties. JSON elements are accessible. E.g. "myscript.sh {ifname} {tq}"'
    )
    parser.add_argument(
        '--node-command',
        help=
        'Execute a command to change link properties. JSON elements are accessible. E.g. "myscript.sh {ifname} {id}"'
    )
    parser.add_argument('--disable-arp',
                        action='store_true',
                        help='Disable ARP support on each interface.')
    parser.add_argument('--disable-multicast',
                        action='store_true',
                        help='Disable Multicast support each interface.')
    parser.add_argument(
        '--remotes',
        help='Distribute nodes and links on remotes described in the JSON file.'
    )
    parser.add_argument(
        '--mtu',
        type=int,
        default=1500,
        help='Set Maximum Transfer Unit (MTU) on each interface.')

    subparsers = parser.add_subparsers(dest='action', required=True)

    parser_change = subparsers.add_parser(
        'apply', help='Create or change a virtual network.')
    parser_change.add_argument(
        'new_state',
        help=
        'JSON file that describes the target topology. Use "none" to remove all network namespaces.'
    )
    subparsers.add_parser(
        'show',
        help=
        'List all Linux network namespaces. Namespace "switch" is the special cable cabinet namespace.'
    )
    subparsers.add_parser(
        'clear',
        help=
        'Remove all Linux network namespaces. Processes still might need to be killed.'
    )

    args = parser.parse_args()

    global disable_arp
    global disable_multicast
    global verbosity
    global mtu

    disable_arp = args.disable_arp
    disable_multicast = args.disable_multicast
    verbosity = args.verbosity
    mtu = args.mtu

    if args.remotes:
        if not os.path.isfile(args.remotes):
            eprint(f'File not found: {args.remotes}')
            stop_all_terminals()
            exit(1)

        with open(args.remotes) as file:
            args.remotes = [Remote.from_json(obj) for obj in json.load(file)]
    else:
        args.remotes = default_remotes

    if args.action == 'clear':
        clear(args.remotes)
    elif args.action == 'show':
        show(args.remotes)
    elif args.action == 'apply':
        apply(args.new_state, args.node_command, args.link_command,
              args.remotes)
    else:
        eprint(f'Invalid command: {args.action}')
        exit(1)
    stop_all_terminals()
示例#17
0
def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('--verbosity',
                        choices=['verbose', 'normal', 'quiet'],
                        default='normal',
                        help='Set verbosity.')
    parser.add_argument(
        '--remotes',
        help='Distribute nodes and links on remotes described in the JSON file.'
    )
    parser.set_defaults(to_state=None)

    subparsers = parser.add_subparsers(dest='action',
                                       required=True,
                                       help='Action')

    parser_start = subparsers.add_parser(
        'start', help='Run start script in every namespace.')
    parser_start.add_argument('protocol',
                              help='Routing protocol script prefix.')
    parser_start.add_argument('to_state',
                              nargs='?',
                              default=None,
                              help='To state')

    parser_stop = subparsers.add_parser(
        'stop', help='Run stop script in every namespace.')
    parser_stop.add_argument('protocol',
                             help='Routing protocol script prefix.')
    parser_stop.add_argument('to_state',
                             nargs='?',
                             default=None,
                             help='To state')

    parser_change = subparsers.add_parser(
        'apply', help='Run stop/start scripts in every namespace.')
    parser_change.add_argument('protocol',
                               help='Routing protocol script prefix.')
    parser_change.add_argument('to_state',
                               nargs='?',
                               default=None,
                               help='To state')

    parser_run = subparsers.add_parser(
        'run', help='Execute any command in every namespace.')
    parser_run.add_argument(
        'command',
        nargs=argparse.REMAINDER,
        help=
        'Shell command that is run. Remote address and namespace id is added to call arguments.'
    )
    parser_run.add_argument('to_state',
                            nargs='?',
                            default=None,
                            help='To state')

    parser_copy = subparsers.add_parser('copy', help='Copy to all remotes.')
    parser_copy.add_argument('source', nargs='+')
    parser_copy.add_argument('destination')

    parser_clear = subparsers.add_parser(
        'clear', help='Run all stop scripts in every namespaces.')

    args = parser.parse_args()

    if args.remotes:
        if not os.path.isfile(args.remotes):
            eprint(f'File not found: {args.remotes}')
            stop_all_terminals()
            exit(1)

        with open(args.remotes) as file:
            args.remotes = [Remote.from_json(obj) for obj in json.load(file)]
    else:
        args.remotes = default_remotes

    check_access(args.remotes)

    global verbosity
    verbosity = args.verbosity

    # get nodes that have been added or will be removed
    (old_ids, new_ids, rmap) = _get_update(args.to_state, args.remotes)

    if args.action == 'start':
        ids = new_ids if args.to_state else list(rmap.keys())

        beg_ms = millis()
        _start_protocol(args.protocol, rmap, ids)
        end_ms = millis()

        if verbosity != 'quiet':
            print('started {} in {} namespaces in {}'.format(
                args.protocol, len(ids), format_duration(end_ms - beg_ms)))
    elif args.action == 'stop':
        ids = old_ids if args.to_state else list(rmap.keys())

        beg_ms = millis()
        _stop_protocol(args.protocol, rmap, ids)
        end_ms = millis()

        if verbosity != 'quiet':
            print('stopped {} in {} namespaces in {}'.format(
                args.protocol, len(ids), format_duration(end_ms - beg_ms)))
    elif args.action == 'apply':
        beg_ms = millis()
        _stop_protocol(args.protocol, rmap, old_ids)
        _start_protocol(args.protocol, rmap, new_ids)
        end_ms = millis()

        if verbosity != 'quiet':
            print('applied {} in {} namespaces in {}'.format(
                args.protocol, len(rmap.keys()),
                format_duration(end_ms - beg_ms)))
    elif args.action == 'clear':
        beg_ms = millis()
        clear(args.remotes)
        end_ms = millis()

        if verbosity != 'quiet':
            print('cleared on {} remotes in {}'.format(
                len(args.remotes), format_duration(end_ms - beg_ms)))
    elif args.action == 'copy':
        beg_ms = millis()
        copy(args.remotes, args.source, args.destination)
        end_ms = millis()

        if verbosity != 'quiet':
            print('copied on {} remotes in {}'.format(
                len(args.remotes), format_duration(end_ms - beg_ms)))
    elif args.action == 'run':
        ids = new_ids if args.to_state else list(rmap.keys())

        for id in ids:
            remote = rmap[id]
            label = remote.address or 'local'
            cmd = f'ip netns exec ns-{id} {" ".join(args.command)} {label} {id}'
            _exec_verbose(remote, cmd)
    else:
        eprint('Unknown action: {}'.format(args.action))
        stop_all_terminals()
        exit(1)

    stop_all_terminals()