def traceroute(net: IPNet, src: str, dst_ip: str, timeout=300) -> List[str]: require_cmd("traceroute", help_str="traceroute is required to run tests") t = 0 old_path_ips = [] # type: List[str] same_path_count = 0 white_space = re.compile(r" +") while t != timeout / 5.: out = net[src].cmd(["traceroute", "-w", "0.05", "-q", "1", "-n", "-m", len(net.routers) + len(net.hosts), dst_ip]) lines = out.split("\n")[1:-1] if "*" not in out and "!" not in out and "unreachable" not in out: path_ips = [str(white_space.split(line)[2]) for line in lines] if len(path_ips) > 0 and path_ips[-1] == str(dst_ip) \ and old_path_ips == path_ips: same_path_count += 1 if same_path_count > 2: # Network has converged return path_ips else: same_path_count = 0 old_path_ips = path_ips else: same_path_count = 0 old_path_ips = [] time.sleep(5) t += 1 return []
def assert_dns_record(node: IPNode, dns_server_address: str, record: DNSRecord, port=53, timeout=60): require_cmd("dig", help_str="dig is required to run tests") server_cmd = "dig @{address} -p {port} -t {rtype} {domain_name}"\ .format(address=dns_server_address, rtype=record.rtype, domain_name=record.domain_name, port=port) out_regex = re.compile(r" *{name}[ \t]+{ttl}[ \t]+IN[ \t]+{rtype}[ \t]+" r"{rdata}" .format(rtype=record.rtype, ttl=record.ttl, name=record.domain_name, rdata=record.rdata)) t = 0 out = node.cmd(server_cmd.split(" ")) got_answer, match = search_dns_reply(out, out_regex) while t < timeout * 2 and match is None: t += 1 time.sleep(.5) out = node.cmd(server_cmd.split(" ")) got_answer, match = search_dns_reply(out, out_regex) assert got_answer, "No answer was received in %s" \ " from server %s in the reply of '%s':\n%s" \ % (node.name, dns_server_address, server_cmd, out) assert match is not None, "The expected data '%s' cannot be found " \ "in the DNS reply of '%s' received by %s from " \ "%s:\n%s" % (out_regex.pattern, server_cmd, node.name, dns_server_address, out)
def check_tcp_connectivity(client: IPNode, server: IPNode, v6=False, server_port=80, server_itf=None, timeout=300) \ -> Tuple[int, bytes, bytes]: require_cmd("nc", help_str="nc is required to run tests") if server_itf is None: server_itf = server.defaultIntf() server_ip = server_itf.ip6 if v6 else server_itf.ip server_cmd = "nc %s -l %d" % ("-6" if v6 else "-4", server_port) server_p = server.popen(server_cmd.split(" ")) t = 0 client_cmd = "nc -z -w 1 -v %s %d" % (server_ip, server_port) client_p = client.popen(client_cmd.split(" ")) while t != timeout * 2 and client_p.wait() != 0: t += 1 if server_p.poll() is not None: out, err = server_p.communicate() assert False, \ "The netcat server used to check TCP connectivity failed" \ " with the output:\n[stdout]\n%s\n[stderr]\n%s" % (out, err) time.sleep(.5) client_p = client.popen(client_cmd.split(" ")) out, err = client_p.communicate() code = client_p.poll() server_p.send_signal(signal.SIGINT) server_p.wait() return code, out, err
def test_require_cmd(cmd, present): try: utils.require_cmd(cmd) assert present, "The command [%s] was found" \ " while it is not present" % cmd except RuntimeError: assert not present, "The command [%s] could not be found" \ " while it is present" % cmd
def start(self, _controllers): """Start Linux bridge""" require_cmd("brctl", help_str="You need brctl to use %s objects" % self.__class__) self.cmd('ifconfig', self, 'down') self.cmd('brctl delbr', self) self.cmd('brctl addbr', self) if self.hub: self.cmd('brctl setageing 0', self) if self.stp: self.cmd('brctl setbridgeprio', self, self.prio) self.cmd('brctl stp', self, 'on') for i in self.intfList(): if self.name in i.name: self.cmd('brctl addif', self, i) self.cmd('brctl setpathcost' ' %s %s %d' % (self.name, i.name, i.params.get('stp_cost', 1))) self.cmd('ifconfig', self, 'up')
def assert_stp_state(switch: IPSwitch, expected_states: Dict[str, str], timeout=60): """ :param switch: The switch to test :param expected_states: Dictionary mapping an interface name to its expected state :param timeout: Time to wait for the stp convergence :return: """ require_cmd("brctl", help_str="brctl is required to run tests") partial_cmd = "brctl showstp" possible_states = "listening|learning|forwarding|blocking" # In these states the STP has not converged ignore_state = "listening", "learning" cmd = ("%s %s" % (partial_cmd, switch.name)) out = switch.cmd(cmd) states = re.findall(possible_states, out) # wait for the ports to be bounded count = 0 while any(item in states for item in ignore_state): if count == timeout: pytest.fail("Timeout of %d seconds while waiting for the spanning" " tree to be computed" % timeout) time.sleep(1) count += 1 out = switch.cmd(cmd) states = re.findall(possible_states, out) interfaces = re.findall(switch.name + r"-eth[0-9]+", out) state_map = {interfaces[i]: states[i] for i in range(len(states))} for itf, _ in expected_states.items(): assert itf in state_map,\ "The port %s of switch %s was not mentioned in the output of " \ "'brctl showstp':\n%s" % (itf, switch.name, out) assert state_map[itf] == expected_states[itf],\ "The state of port %s of switch %s wasn't correct: excepted '%s' " \ "got '%s'"\ % (itf, switch.name, expected_states[itf], state_map[itf])
def register_daemon(self, cls, **daemon_opts): """Add a new daemon to this configuration :param cls: Daemon class or object, or a 2-tuple (Daemon, dict) :param daemon_opts: Options to set on the daemons""" try: cls, kw = cls daemon_opts.update(kw) except TypeError: pass if cls.NAME in self._daemons: return if not isinstance(cls, Daemon): if issubclass(cls, Daemon): cls = cls(self._node, **daemon_opts) else: raise TypeError('Expected an object or a subclass of ' 'Daemon, got %s instead' % cls) else: cls.options.update(daemon_opts) self._daemons[cls.NAME] = cls require_cmd(cls.NAME, 'Could not find an executable for a daemon!')
def host_connected(net: IPNet, v6=False, timeout=0.5, translate_address=True) \ -> bool: require_cmd("nmap", help_str="nmap is required to run tests") for src in net.hosts: for dst in net.hosts: if src != dst: dst.defaultIntf().updateIP() dst.defaultIntf().updateIP6() if translate_address: dst_ip = dst.defaultIntf().ip6 if v6 \ else dst.defaultIntf().ip else: dst_ip = dst cmd = "nmap%s -sn -n --max-retries 5 --max-rtt-timeout %dms %s"\ % (" -6" if v6 else "", int(timeout * 1000), dst_ip) out = src.cmd(cmd.split(" ")) if "0 hosts up" in out: return False # In case of flooding, hosts might not answer # So, we wait a bit before testing the next pair of hosts time.sleep(0.1) return True
def sr_path(net: IPNet, src: str, dst_ip: str, timeout=1, through=()) \ -> List[str]: require_cmd("tshark", help_str="tshark is required to run tests") require_cmd("nmap", help_str="nmap is required to run tests") # Check connectivity ping_cmd = "ping -6 -c 1 -W %d %s" % (int(timeout), dst_ip) out = net[src].cmd(shlex.split(ping_cmd)) if ", 0% packet loss" not in out: return [] # Start captures of SYN packets to port 80 tsharks = [] try: for n in net.routers + net.hosts: p = n.popen(shlex.split("tshark -n -i any -f 'ip6'" " -w /tmp/{}.pcap".format(n.name))) tsharks.append(p) time.sleep(15) # Wait for tshark to start # Launch ping out = net[src].cmd(shlex.split(ping_cmd)) assert "100% packet loss" not in out, \ "Connectivity from %s to %s is not ensured," \ " so we cannot infer the path." % (src, dst_ip) time.sleep(1) # Wait for tshark to register the info for p in tsharks: assert p.poll() is None, "tshark stopped unexpectedly:" \ "stderr '{}'".format(p.stderr.read()) finally: # Stop captures for p in tsharks: p.send_signal(signal.SIGINT) p.wait() # Retrieve packet captures captures = {} # type: Dict[str, List[Tuple[float, str]]] for n in net.routers + net.hosts: out = n.cmd(shlex.split("tshark -n -r /tmp/{}.pcap -T fields " "-E separator=, " "-e icmpv6.type -e ipv6.dst -e frame.time_epoch" .format(n.name))) data = out.split("\n")[1:-1] for line in data: values = line.strip().split(",") if len(values) < 3: continue icmp_type = values[0] data_dst = values[1] data_time = values[-1] if icmp_type == "128": captures.setdefault(n.name, []) \ .append((float(data_time), data_dst)) # Analyze results packet_received = {} # type: Dict[str, List[Tuple[float, str]]] for n, packets in captures.items(): for data_time, destination in packets: packet_received.setdefault(destination, []).append((data_time, n)) sub_paths = {} # type: Dict[str, List[str]] for dest in packet_received: ordered_reception = sorted(packet_received[dest]) sub_paths[dest] = [] for _, n in ordered_reception: if len(sub_paths[dest]) == 0 or sub_paths[dest][-1] != n: sub_paths[dest].append(n) # Order sub paths with the trough list path = [] # type: List[str] for intermediate in through: found = False try: intermediate_ips = [ip.ip.compressed for itf in net[intermediate].intfList() for ip in itf.ip6s(exclude_lls=True)] except KeyError: intermediate_ips = [intermediate] for ip in intermediate_ips: if ip in sub_paths: found = True path.extend(sub_paths[ip]) break if not found: return path path.extend(sub_paths[dst_ip]) # Remove duplicates compressed_path = [] # type: List[str] for n in path: if len(compressed_path) == 0 or n != compressed_path[-1]: compressed_path.append(n) # Remove source to get a similar output to traceroute path = [net[n].intf().ip6 for n in compressed_path][1:] return path