def tear_down(self): if not self.up: logger.warning("wireguard interface already disabled: {}", self.interface) return logger.debug("disabling wireguard interface: {}", self.interface) # Disable interface with "ip link set down dev..." exec_command([ "ip", "link", "set", "down", "dev", self.interface], root=True, fail_msg="failed to disable interface: {}".format(self.interface), exception=WireGuardError) exec_command([ "ip", "address", "flush", "dev", self.interface], root=True, fail_msg="failed to reset addresses on interface: {}".format(self.interface), exception=WireGuardError) logger.activity("wireguard interface disabled: {}", self.interface) # Mark interface as down self.up = False
def systemd_resolved_disable(): if systemd_resolved_running(): exec_command(["systemctl", "disable", "--now", "systemd-resolved"], root=True, fail_msg="failed to disable systemd-resolved") return True return False
def ipv4_disable_output_nat(nic, ignore_errors=False): exec_command([ "iptables", "-t", "nat", "-D", "POSTROUTING", "-o", str(nic), "-j", "MASQUERADE" ], root=True, noexcept=ignore_errors, quiet=ignore_errors)
def ipv4_del_route_to_network(net_addr, net_nic, net_gw): exec_command([ "ip", "route", "del", str(net_addr), "via", str(net_gw), "dev", str(net_nic) ], fail_msg="failed to remove route: {}({}) <--> {}".format( str(net_gw), str(net_nic), str(net_addr)))
def _encode_file_png(file_in, file_out): file_out = pathlib.Path(file_out) file_out.parent.mkdir(parents=True, exist_ok=True) exec_command([ "sh", "-c", " ".join( ["qrencode", "-t", "png", "-o", str(file_out), "<", str(file_in)]) ], fail_msg="failed to encode PNG qr code")
def ipv4_disable_source_nat(nic, src_network): logger.debug("disabling source NAT on {} for {}", nic, src_network) exec_command(["iptables", "-D", "INPUT", "-i", str(nic), "-j", "ACCEPT"], root=True) exec_command([ "iptables", "-t", "nat", "-D", "POSTROUTING", "-s", str(src_network), "-o", str(nic), "-j", "MASQUERADE" ], root=True) logger.activity("[disabled] source NAT on {} for {} ", nic, src_network)
def _run_test(self): result = exec_command(["ip", "-o", "route"]) def check_route(r): for n in self.interfaces: if f"dev {n}" in r: return True return False def mkroute(r): subnet = ipaddress.ip_network(r.split(" ")[0]) nic = r.split(" ")[2] gw = ipaddress.ip_address("0.0.0.0") peer = "" return LocalRoute(subnet=subnet, nic=nic, gw=gw, peer=peer) results = frozenset(result.stdout.decode("utf-8").split("\n")[:-1]) results = filter(check_route, results) # Filter by finding peer in cell_cfg.backbone and checking # that route isn't for a backbone network routes = [mkroute(r) for r in results] if routes: logger.trace("found routes for {}: {}", self.interfaces, routes) self.router.listener.on_route_monitor_result(self.router, routes) else: logger.warning("no backbone routes detected for {}", self.interfaces)
def systemd_resolved_running(): res = exec_command(["killall", "-0", "systemd-resolved"], root=True, noexcept=True, quiet=True, fail_msg="failed to check systemd-resolved status") return res.returncode == 0
def peers(self): result = exec_command( ["wg", "show", str(self.interface), "peers"], root=True, fail_msg="failed to get current peers for interface: {}".format(self.interface), exception=WireGuardError) peers = list(filter(lambda v: len(v) > 0, result.stdout.decode("utf-8").split("\n"))) return peers
def _list_peers(self): result = exec_command(["wg", "show", str(self.interface), "peers"], quiet=True, root=True, fail_msg="failed to get peers for interface: {}".format(self.interface), exception=WireGuardError) peers = set(decode_output(result.stdout)) logger.trace("current peers [{}]: {}", self.interface, peers) return peers
def build_connextddspy(nddshome, dst_dir, arch, keep=False): # Create a temporary directory for storing build files tmp_dir = tempfile.mkdtemp(prefix=f"connextdds-py-{nddshome.name}-", suffix="-context") tmp_dir = pathlib.Path(tmp_dir) nddsarch = UvnDefaults["dds"]["connext"]["arch"].get(container_arch) if not nddsarch: raise ValueError(f"unsupported architecture: {arch}") try: # Clone connextdds-py repository repo_url = UvnDefaults["dds"]["connext"]["py"]["git"] repo_dir = tmp_dir / "connextdds-py" exec_command( ["git", "clone", repo_url, repo_dir], fail_msg="failed to clone git repository: {}".format(repo_url)) # Run configure.py logger.warning( "building connextdds-py wheel (this might take some time)") cpu_count = multiprocessing.cpu_count() exec_command( ["python", "configure.py", "-j", str(cpu_count), nddsarch], cwd=repo_dir, fail_msg="failed to clone git repository: {}".format(repo_url)) # Copy wheel to destination directory: py_vers = "{}{}".format(sys.version_info.major, sys.version_info.minor) whl_name = self.name = UvnDefaults["docker"]["context"][ "connextdds_wheel_fmt"].format(py_vers, py_vers, arch) whl_path = repo_dir / whl_name shutil.copy2(str(whl_path), str(dst_dir)) except Exception as e: logger.exception(e) logger.error("failed to build wheel for connextdds-py: {}", arch) raise (e) finally: if not keep: shutil.rmtree(str(tmp_dir)) else: logger.warning("[tmp] not deleted: {}", tmp_dir) return whl_path
def run(self): logger.activity("[{}] starting up...", self._daemon) try: self._sem_started.release() exec_command(["killall", "-9", self._daemon], fail_msg=f"failed to kill {self._daemon}", quiet=True, noexcept=True) exec_command([ self._daemon, "-f", self._config, "-i", self._pid, "-z", self._socket ], fail_msg=f"failed to run {self._daemon}") except Exception as e: logger.exception(e) logger.error("error in {} thread", self._daemon) raise e finally: logger.activity("[{}] stopped", self._daemon)
def ipv4_list_routes(oneline=False, resolve=True, split=True): if not oneline: cmd = ["ip", "route"] else: cmd = ["ip", "-o", "route"] result = exec_command(cmd, fail_msg="failed to list routes") results = result.stdout.decode("utf-8") if split: results = frozenset(results.split("\n")[:-1]) return results
def delete(self): if not self.created: logger.warning("wireguard interface not deleted: {}", self.interface) return logger.debug("deleting wireguard interface: {}", self.interface) # Remove interface with "ip link delete dev..." exec_command([ "ip", "link", "delete", "dev", self.interface], root=True, fail_msg="failed to add interface: {}".format(self.interface), exception=WireGuardError) logger.debug("deleted wireguard interface: {}", self.interface) # Mark interface as up self.created = False
def _update_allowed_ips(self, peer, allowed_ips): logger.trace("updating allowed ips for peer {} on {}: {}", peer, self.interface, list(map(str, allowed_ips))) # sort ips for tidyness in `wg show`'s output allowed_ips_str = sorted(map(str,allowed_ips)) exec_command([ "wg", "set", self.interface, "peer", peer, "allowed-ips", ",".join(allowed_ips_str)], root=True, fail_msg=f"failed to get set allowed-ips on interface: {self.interface}", exception=WireGuardError) allowed_ips_set = self._list_allowed_ips_peer(peer) if allowed_ips ^ allowed_ips_set: logger.warning("[unexpected allowed][{}] {}", self.interface, peer) logger.warning("[set][{}] {}: {}", self.interface, peer, allowed_ips) logger.warning("[found][{}] {}: {}", self.interface, peer, allowed_ips_set)
def _list_handshakes(self): result = exec_command(["wg", "show", str(self.interface), "latest-handshakes"], quiet=True, root=True, fail_msg="failed to get latest handshakes for interface: {}".format(self.interface), exception=WireGuardError) handshakes = {} for line in decode_output(result.stdout): l_split = list(filter(len, line.split())) handshakes[l_split[0]] = Timestamp.unix(l_split[1]) logger.trace("current handshakes [{}]: {}", self.interface, handshakes) return handshakes
def _tracepath_test(self, peer_count, peer_i, p_name, p_rec): ts_check = Timestamp.now() result = exec_command( ["tracepath", "-c", str(self._ping_count), str(p_rec["address"])], fail_msg="failed to ping peer: {} [{}]".format(p_name, p_rec["address"]), # don't throw an exception on error noexcept=True, # don't print any error message quiet=True) peer_ok = result.returncode == 0 if not peer_ok: unavail_peers.add(p_name) self._update_and_notify(peer_i, peer_count, p_name, peer_ok, ts_check)
def exec(cmd_id): cmd_id_p = cmd_id.split(".") cmds = Vtysh.commands for p in cmd_id_p: cmds = cmds[p] cmd = ["vtysh", "-E"] for c in cmds: cmd.extend(["-c", str(c)]) result = exec_command(cmd, fail_msg="failed to perform vtysh command", root=True) return result.stdout.decode("utf-8")
def _list_allowed_ips(self): result = exec_command(["wg", "show", str(self.interface), "allowed-ips"], quiet=True, root=True, fail_msg="failed to get endpoints for interface: {}".format(self.interface), exception=WireGuardError) allowed_ips = {} for line in decode_output(result.stdout): l_split = list(filter(len, line.split())) ips = set(map(ipaddress.ip_network, filter(lambda s: s != "(none)", filter(len, l_split[1:])))) allowed_ips[l_split[0]] = ips logger.trace("current allowed IPs [{}]: {}", self.interface, allowed_ips) return allowed_ips
def _list_transfer(self): result = exec_command(["wg", "show", str(self.interface), "transfer"], quiet=True, root=True, fail_msg="failed to get transfer stats for interface: {}".format(self.interface), exception=WireGuardError) transfers = {} for line in decode_output(result.stdout): l_split = list(filter(len, line.split())) transfers[l_split[0]] = { "recv": int(l_split[1]), "send": int(l_split[2]) } logger.trace("current transfer stats [{}]: {}", self.interface, transfers) return transfers
def create(self): if self.created: logger.warning("wireguard interface already created: {}", self.interface) return logger.debug("creating wireguard interface: {}", self.interface) # Check if interface already exists with "ip link show..." result = exec_command([ "ip", "link", "show", self.interface], root=True, noexcept=True, quiet=True) intf_exists = result.returncode == 0 if intf_exists: # Delete interface with "ip link delete dev..." logger.debug("deleting existing wireguard interface: {}", self.interface) exec_command([ "ip", "link", "delete", "dev", self.interface], root=True, fail_msg="failed to delete interface: {}".format(self.interface), exception=WireGuardError) # Add interface with "ip link add dev..." exec_command([ "ip", "link", "add", "dev", self.interface, "type", "wireguard"], root=True, fail_msg="failed to add interface: {}".format(self.interface), exception=WireGuardError) logger.debug("created wireguard interface: {}", self.interface) # Mark interface as up self.created = True
def ipv4_enable_forward(nic): exec_command(["iptables", "-A", "FORWARD", "-i", str(nic), "-j", "ACCEPT"], root=True) exec_command(["iptables", "-A", "FORWARD", "-o", str(nic), "-j", "ACCEPT"], root=True) exec_command(["iptables", "-A", "INPUT", "-i", str(nic), "-j", "ACCEPT"], root=True)
def run(self): # try: logger.debug("starting routing service") result = exec_command( [ self._bin, "-verbosity", str(self._verbosity), "-cfgFile", str(self.cfg_file), "-cfgName", self.cfg_name ], # "|", "tee", "-a", str(self.log_file)], # shell=True, output=self.log_file, fail_msg= f"failed to run routing service. see {self.log_file} for more info", noexcept=True) logger.activity("routing service exited")
def ipv4_default_gateway(): result = exec_command(["ip", "route"], fail_msg="failed to get kernel routes") for line in decode_output(result.stdout): if not line.startswith("default via "): continue l_split = list(filter(len, line.split())) try: gw = ipaddress.ip_address(l_split[2]) except Exception as e: logger.debug("failed to parse as gateway address: {}", l_split[2]) continue return gw raise RuntimeError("failed to determine default gateway")
def _clone_uno(tmp_dir, keep=False, dev=True): # Clone the "uno" git repository repo_dir = tmp_dir / UvnDefaults["docker"]["context"]["repo_dir"] repo_dir_clone = repo_dir.with_name("{}.tmp".format(repo_dir.name)) repo_url = UvnDefaults["docker"]["context"]["repo_url_fmt"].format( UvnDefaults["docker"]["context"]["repo_proto"], os.environ.get(UvnDefaults["docker"]["env"]["oauth_token"]), UvnDefaults["docker"]["context"]["repo_url_base"]) repo_branch = UvnDefaults["docker"]["context"]["repo_branch"] if not dev: logger.debug("clone git repo {} to {}", repo_url, repo_dir_clone) exec_command( ["git", "clone", "-b", repo_branch, repo_url, repo_dir_clone], fail_msg="failed to clone git repository: {}".format(repo_url)) # Create an archived copy logger.debug("archive git repo to {}", repo_dir) repo_tar = repo_dir.with_name("{}.tar".format(repo_dir.name)) exec_command( [ "git", "archive", repo_branch, "--format", "tar", "-o", repo_tar ], cwd=repo_dir_clone, fail_msg="failed to archive git repository: {}".format( repo_tar)) repo_dir.mkdir(parents=True, exist_ok=True) exec_command( ["tar", "xvf", repo_tar, "-C", repo_dir], fail_msg="failed to extract repository: {}".format(repo_dir)) else: # Copy uno from this current copy import libuno uno_path = pathlib.Path(libuno.__file__).parent.parent logger.debug("copying uno from {}", uno_path) shutil.copytree(str(uno_path), str(repo_dir), ignore=DockerController.filter_uvn_files) # Delete clone directory and tar file if not keep and not dev: repo_tar.unlink() shutil.rmtree(str(repo_dir_clone)) return repo_dir
def ipv4_disable_forward(nic, ignore_errors=False): exec_command(["iptables", "-D", "FORWARD", "-i", str(nic), "-j", "ACCEPT"], root=True, noexcept=ignore_errors, quiet=ignore_errors) exec_command(["iptables", "-D", "FORWARD", "-o", str(nic), "-j", "ACCEPT"], root=True, noexcept=ignore_errors, quiet=ignore_errors) exec_command(["iptables", "-D", "INPUT", "-i", str(nic), "-j", "ACCEPT"], root=True, noexcept=ignore_errors, quiet=ignore_errors)
def _list_endpoints(self): result = exec_command(["wg", "show", str(self.interface), "endpoints"], quiet=True, root=True, fail_msg="failed to get endpoints for interface: {}".format(self.interface), exception=WireGuardError) endpoints = {} for line in decode_output(result.stdout): l_split = list(filter(len, line.split())) endp_split = list(filter(len, l_split[1].split(":"))) try: addr = ipaddress.ip_address(endp_split[0]) port = int(endp_split[1]) except Exception as e: addr = "<unknown>" port = "<unknown>" endpoints[l_split[0]] = { "address": addr, "port": port } logger.trace("current endpoints [{}]: {}", self.interface, endpoints) return endpoints
def stop(self): logger.activity("signaling routing service to exit") exec_command(["killall", "-SIGTERM", "rtiroutingservice"], fail_msg="failed to signal routing service", noexcept=True) self.join()
def _initialize_runner_context(tmp_dir, basedir, dockerfile, container_arch, connext_helper, keep=False, copy_uno=False, copy_uvn=False, build_wheel=False, dev=False, connext=False): logger.activity("[context] initializing: {}", tmp_dir) context_data = [] extra_args = {} if copy_uno and not dev: repo_dir = DockerController._clone_uno(tmp_dir, keep=keep, dev=dev) context_data.append(repo_dir.name) if copy_uvn: uvn_dir = DockerController._clone_uvn(tmp_dir, basedir, keep=keep) context_data.append(uvn_dir.name) # Retrieve a pre-built wheel file to install connextdds-py connextdds_wheel = None if not build_wheel: try: connextdds_wheel = StaticData.connextdds_wheel(container_arch) except Exception as e: logger.warning( "no prebuilt connextdds-py wheel found for architecture {}" ) if not connextdds_wheel or build_wheel: connextdds_wheel = connext_helper.py.build(container_arch) dds_whl_path = tmp_dir / connextdds_wheel.name connextdds_wheel.copy_to(dds_whl_path) context_data.append(dds_whl_path.name) extra_args["CONNEXTDDS_WHEEL"] = connextdds_wheel.name if connext: # Copy Connext DDS libraries and routing service dds_path = tmp_dir / UvnDefaults["dds"]["home"] connextdds_arc = UvnDefaults["dds"]["connext"]["arch"][ container_arch] connext_helper.copy_to(dds_path, archs=[connextdds_arc]) context_data.append(dds_path.name) # Instantiate Dockerfile dockerfile_path = tmp_dir / "Dockerfile" base_image = None if container_arch == "x86_64": if sys.version_info.minor == 6: base_image = "ubuntu:18.04" elif sys.version_info.minor == 8: base_image = "ubuntu:20.04" elif container_arch == "armv7l": if sys.version_info.minor == 7: base_image = "balenalib/raspberry-pi-debian:latest" else: raise ValueError( f"unsupported container architecture: {container_arch}") if not base_image: raise RuntimeError( f"Python 3.{sys.version_info.minor} not supported in {container_arch} uno containers yet" ) dockerfile_tmplt = Dockerfile(base_image=base_image, dev=dev, ndds=False, rpi_extra=container_arch == "armv7l") render(dockerfile_tmplt, "Dockerfile", to_file=dockerfile_path) context_data.append("Dockerfile") # Instantiate entrypoint script entrypoint_path = tmp_dir / "entrypoint.sh" with entrypoint_path.open("w") as output: entrypoint_stream = StaticData.script("entrypoint.sh", binary=False) shutil.copyfileobj(entrypoint_stream, output) context_data.append("entrypoint.sh") # Create a tar containing the custom build context context_tar = tmp_dir / UvnDefaults["docker"]["context"]["tar"] cmd_args = ["tar", "cvf", context_tar] cmd_args.extend(context_data) exec_command( cmd_args, cwd=tmp_dir, fail_msg="failed to archive build context: {}".format(context_tar)) return context_tar, extra_args
def bring_up(self): if self.up: logger.warning("wireguard interface already active: {}", self.interface) return logger.debug("activating wireguard interface: {} [{}]", self.interface, self.interface_address) # Disable interface with "ip link set down dev..." exec_command([ "ip", "link", "set", "down", "dev", self.interface], root=True, fail_msg="failed to disable interface: {}".format(self.interface), exception=WireGuardError) exec_command([ "ip", "address", "flush", "dev", self.interface], root=True, fail_msg="failed to reset addresses on interface: {}".format(self.interface), exception=WireGuardError) # Configure interface address with "ip address add dev..." exec_command([ "ip", "address", "add", "dev", self.interface, "{}/{}".format(self.interface_address, self.interface_address_mask)], root=True, fail_msg="failed to configure interface address: {} {}".format( self.interface, self.interface_address), exception=WireGuardError) # Generate a temporary file with wg configuration tmp_file_fd, tmp_file_path = tempfile.mkstemp( prefix="{}-".format(self.interface), suffix="-wgconf") tmp_file_path = pathlib.Path(str(tmp_file_path)) try: with tmp_file_path.open("w") as output: output.write(self.config) output.flush() # Set wireguard configuration with "wg setconf..." exec_command([ "wg", "setconf", self.interface, str(tmp_file_path)], root=True, fail_msg="failed to configure wireguard: {}".format(self.interface), exception=WireGuardError) finally: os.close(tmp_file_fd) if not self.keep: tmp_file_path.unlink() else: logger.warning("[tmp] not deleted: {}", tmp_file_path) # Activate interface with "ip link set up dev..." exec_command([ "ip", "link", "set", "up", "dev", self.interface], root=True, fail_msg="failed to activate wireguard: {}".format(self.interface), exception=WireGuardError) # Allow configure IP addresses for p in self._list_peers(): for a in self.allowed_ips: self.allow_ips(p, a) logger.activity("wireguard interface active: {} [{}]", self.interface, self.interface_address) # Mark interface as up self.up = True