def power_on_do(self, target): pidfile = os.path.join(target.state_dir, "socat-" + self.tunnel_id + ".pid") cmdline = [ self.path, "-ly", "-lp", self.tunnel_id, "%s-LISTEN:%d,bind=%s,fork,reuseaddr" % ( self.proto, self.local_port, self.local_addr), "%s:%s:%s" % (self.proto, self.remote_addr, self.remote_port) ] try: p = subprocess.Popen(cmdline, shell = False, cwd = target.state_dir, close_fds = True, stderr = subprocess.STDOUT) with open(pidfile, "w+") as pidf: pidf.write("%s" % p.pid) except OSError as e: raise self.start_e("socat failed to start: %s", e) pid = commonl.process_started( pidfile, self.path, verification_f = commonl.tcp_port_busy, verification_f_args = (self.local_port,), tag = "socat", log = target.log) # systemd might complain with # # Supervising process PID which is not our child. We'll most # likely not notice when it exits. # # Can be ignored if pid == None: raise self.start_e("socat failed to start") ttbl.daemon_pid_add(pid) # FIXME: race condition if it died?
def power_on_do(self, target): pidfile = os.path.join(target.state_dir, "adb-" + self._id + ".pid") cmdline = [ self.path ] if self.target_serial_number: # If the thing is connected via USB cmdline += [ "-s", self.target_serial_number ] cmdline += [ # we are going to listen on this port on all interfaces "-a", "-P", str(self.server_port), "nodaemon", "server" ] try: target.log.error('DEBUG %s' % cmdline) env = dict(os.environ) if self.debug: env['ADB_TRACE'] = "all" p = subprocess.Popen(cmdline, shell = False, cwd = target.state_dir, env = env, close_fds = True, stderr = subprocess.STDOUT) with open(pidfile, "w+") as pidf: pidf.write("%s" % p.pid) except OSError as e: raise self.start_e("adb failed to start: %s", e) pid = commonl.process_started( pidfile, self.path, verification_f = commonl.tcp_port_busy, verification_f_args = (self.server_port,), tag = "adb", log = target.log) # systemd might complain with # # Supervising process PID which is not our child. We'll most # likely not notice when it exits. # # Can be ignored if pid == None: raise self.start_e("adb failed to start") ttbl.daemon_pid_add(pid) # FIXME: race condition if it died? # Connected via TCP/IP? tell the daemon to connect if self.target_port: subprocess.check_output([ self.path, "-H", "localhost", "-P", str(self.server_port), "connect", "%s:%d" % (self.target_port, self.target_port) ]) target.property_set("adb.port", str(self.server_port))
def _dhcpd_start(self): # Fire up the daemons dhcpd_leases_name = os.path.join(self.state_dir, "dhcpd.leases") # Create the leases file if it doesn't exist with open(dhcpd_leases_name, 'a'): # touch the access/modify time to now os.utime(dhcpd_leases_name, None) if self.ip_mode == 4: ip_mode = "-4" else: ip_mode = "-6" args = [ # Requires CAP_NET_BIND_SERVICE CAP_NET_ADMIN #"strace", "-f", "-s2048", "-o/tmp/kk.log", "dhcpd", "-d", "-q", # Run it in foreground, so the process group owns it and # kills it when exiting "-f", ip_mode, "-cf", os.path.join(self.state_dir, "dhcpd.conf"), "-lf", dhcpd_leases_name, "-pf", self.dhcpd_pidfile, self._params['if_name'], ] logfile_name = os.path.join(self.state_dir, "dhcpd.log") so = open(logfile_name, "wb") try: subprocess.Popen(args, shell = False, cwd = self.state_dir, close_fds = True, stdout = so, stderr = subprocess.STDOUT) except OSError as e: raise self.start_e("DHCPD failed to start: %s", e) pid = commonl.process_started( self.dhcpd_pidfile, self.dhcpd_path, verification_f = os.path.exists, verification_f_args = (self.dhcpd_pidfile,), tag = "dhcpd", log = self.log) # systemd might complain with # # Supervising process PID which is not our child. We'll most # likely not notice when it exits. # # Can be ignored if pid == None: raise self.start_e("dhcpd failed to start") ttbl.daemon_pid_add(pid) # FIXME: race condition if it died?
def power_on_do(self, target): """ Start the daemon, generating first the config file """ file_prefix = os.path.join( target.state_dir, "rsync-%s:%d" % (self.address, self.port)) pidfile = file_prefix + ".pid" with open(file_prefix + ".conf", "w+") as conff: conff.write("""\ # We run the daemon as root and need to run as root so we can access # folders that have root-only weird permissions # FIXME: we could also CAP_DAC_READ_SEARCH or similar [images] path = {0.share_path} read only = {0.read_only} timeout = 300 """.format(self)) if self.uid: conff.write("uid = %s" % self.uid) if self.gid: conff.write("gid = %s" % self.gid) def _preexec_fn(): # We need this to access image files to serve that are # owned by root (because that's what the image is and we # want to share them with the same bits--even if we mapped # user to something else, some attributes and different # user bits would force us to do something like this) prctl.cap_effective.dac_read_search = True # rsync chroots for safety prctl.cap_effective.sys_chroot = True return cmdline = [ "rsync", "--daemon", "--no-detach", "--address", self.address, "--port", str(self.port), "--config", file_prefix + ".conf" ] try: p = subprocess.Popen( cmdline, shell = False, cwd = target.state_dir, close_fds = True, stderr = subprocess.STDOUT, preexec_fn = _preexec_fn) with open(pidfile, "w+") as pidf: pidf.write("%s" % p.pid) except OSError as e: raise self.start_e("rsync failed to start: %s" % e) pid = commonl.process_started( pidfile, self.path, verification_f = commonl.tcp_port_busy, verification_f_args = (self.port,), tag = "rsync", log = target.log) # systemd might complain with # # Supervising process PID which is not our child. We'll most # likely not notice when it exits. # # Can be ignored if pid == None: raise self.start_e("rsync failed to start") ttbl.daemon_pid_add(pid) # FIXME: race condition if it died?
def on(self, target, _component): # Bring up the lower network interface; lower is called # whatever (if it is a physical device) or _bNAME; bring it # up, make it promiscuous mode = self._get_mode(target) if mode == 'vlan': # our lower is a physical device, our upper is a device # which till tag for eth vlan %(vlan) ifname = commonl.if_find_by_mac(target.tags['mac_addr'], physical=True) commonl.if_remove_maybe("b%(id)s" % target.kws) kws = dict(target.kws) kws['ifname'] = ifname subprocess.check_call( "/usr/sbin/ip link add" " link %(ifname)s name b%(id)s" " type vlan id %(vlan)s" #" protocol VLAN_PROTO" #" reorder_hdr on|off" #" gvrp on|off mvrp on|off loose_binding on|off" % kws, shell=True) subprocess.check_call( # bring lower up "/usr/sbin/ip link set dev %s up promisc on" % ifname, shell=True) elif mode == 'physical': ifname = commonl.if_find_by_mac(target.tags['mac_addr']) subprocess.check_call( # bring lower up "/usr/sbin/ip link set dev %s up promisc on" % ifname, shell=True) self._if_rename(target) elif mode == 'virtual': # We do not have a physical device, a bridge, to serve as # lower commonl.if_remove_maybe("_b%(id)s" % target.kws) subprocess.check_call("/usr/sbin/ip link add" " name _b%(id)s" " type bridge" % target.kws, shell=True) subprocess.check_call("/usr/sbin/ip link add" " link _b%(id)s name b%(id)s" " type macvlan mode bridge; " % target.kws, shell=True) subprocess.check_call( # bring lower up "/usr/sbin/ip link set" " dev _b%(id)s" " up promisc on" % target.kws, shell=True) else: raise AssertionError("Unknown mode %s" % mode) # Configure the IP addresses for the top interface subprocess.check_call( # clean up existing address "/usr/sbin/ip add flush dev b%(id)s " % target.kws, shell=True) subprocess.check_call( # add IPv6 # if this fails, check Network Manager hasn't disabled ipv6 # sysctl -a | grep disable_ipv6 must show all to 0 "/usr/sbin/ip addr add" " %(ipv6_addr)s/%(ipv6_prefix_len)s dev b%(id)s " % target.kws, shell=True) subprocess.check_call( # add IPv4 "/usr/sbin/ip addr add" " %(ipv4_addr)s/%(ipv4_prefix_len)d" " dev b%(id)s" % target.kws, shell=True) # Bring up the top interface, which sets up ther outing subprocess.check_call( "/usr/sbin/ip link set dev b%(id)s up promisc on" % target.kws, shell=True) target.fsdb.set('power_state', 'on') # Start tcpdump on the network? # # The value of the tcpdump property, if not None, is the # filename we'll capture to. tcpdump = target.fsdb.get('tcpdump') if tcpdump: assert not os.path.sep in tcpdump \ and tcpdump != "" \ and tcpdump != os.path.pardir \ and tcpdump != os.path.curdir, \ "Bad filename for TCP dump capture '%s' specified as " \ " value to property *tcpdump*: must not include" % tcpdump # per ttbd:make_ticket(), colon splits the real username # from the ticket owner = target.owner_get().split(":")[0] assert owner, "BUG? target not owned on power on?" capfile = os.path.join(target.files_path, owner, tcpdump) # Because it is in the user's area, # we assume the user knows what he is doing to overwrite it, # so we'll remove any first commonl.rm_f(capfile) pidfile = os.path.join(target.state_dir, "tcpdump.pid") logfile = os.path.join(target.state_dir, "tcpdump.log") cmdline = [ "/usr/sbin/tcpdump", "-U", "-i", "_b%(id)s" % target.kws, "-w", capfile ] try: logf = open(logfile, "a") target.log.info("Starting tcpdump with: %s", " ".join(cmdline)) p = subprocess.Popen(cmdline, shell=False, cwd=target.state_dir, close_fds=True, stdout=logf, stderr=subprocess.STDOUT) except OSError as e: raise RuntimeError("tcpdump failed to start: %s" % e) ttbl.daemon_pid_add(p.pid) # FIXME: race condition if it died? with open(pidfile, "w") as pidfilef: pidfilef.write("%d" % p.pid) pid = commonl.process_started( # Verify it started pidfile, "/usr/sbin/tcpdump", verification_f=os.path.exists, verification_f_args=(capfile, ), timeout=20, tag="tcpdump", log=target.log) if pid == None: raise RuntimeError("tcpdump failed to start after 5s")
def on(self, target, component): stderrf_name = os.path.join(target.state_dir, component + "-" + self.name + ".stderr") kws = dict(target.kws) kws.update(self.kws) kws['component'] = component # render the real commandline against kws _cmdline = [] for i in self.cmdline: # some older Linux distros complain if this string is unicode _cmdline.append(str(i % kws)) target.log.info("%s: command line: %s" % (component, " ".join(_cmdline))) if self.env_add: env = dict(os.environ) env.update(self.env_add) else: env = os.environ pidfile = self.pidfile % kws commonl.rm_f(pidfile) stderrf = open(stderrf_name, "w+") try: p = subprocess.Popen(_cmdline, env=env, cwd=target.state_dir, stderr=stderrf, bufsize=0, shell=False, universal_newlines=False) if self.mkpidfile: with open(pidfile, "w+") as pidf: pidf.write("%s" % p.pid) except TypeError as e: # This happens on misconfiguration ## TypeError: execve() arg 3 contains a non-string value if 'execve() arg 3' in str(e): target.log.exception( "Ensure environment settings are not set to None", e) if 'execve()' in str(e): target.log.exception("Possible target misconfiguration: %s", e) count = 0 for i in _cmdline: target.log.error("cmdline %d: [%s] %s", count, type(i).__name__, i) count += 1 for key, val in env.iteritems(): target.log.error("env %s: [%s] %s", key, type(val).__name__, val) raise except OSError as e: raise self.start_e("%s: %s failed to start: %s" % (component, self.name, e)) del stderrf # we don't care for this file here if self.precheck_wait: time.sleep(self.precheck_wait) pid = commonl.process_started(pidfile, self.path, component + "-" + self.name, target.log, self.verify, (_cmdline, )) if pid == None: raise self.start_e("%s: %s failed to start" % (component, self.name)) ttbl.daemon_pid_add(pid)
def put_tunnel(self, target, who, args, _files, _user_path): """ Setup a TCP/UDP/SCTP v4 or v5 tunnel to the target Parameters are same as :meth:`ttbl.tt_interface.request_process` Parameters specified in the *args* dictionary from the HTTP interface: :param str ip_addr: target's IP address to use (it must be listed on the targets's tags *ipv4_address* or *ipv6_address*). :param int port: port to redirect to :param str protocol: Protocol to tunnel: {udp,sctp,tcp}[{4,6}] :returns dict: dicionary with a single key *result* set ot the *local_port* where to TCP connect to reach the tunnel. """ ip_addr, port, protocol, tunnel_id = self._check_args( self.arg_get(args, 'ip_addr', basestring), self.arg_get(args, 'port', int), self.arg_get(args, 'protocol', basestring), ) self._ip_addr_validate(target, ip_addr) with target.target_owned_and_locked(who): for tunnel_id in target.fsdb.keys("interfaces.tunnel.*.protocol"): prefix = tunnel_id[:-len(".protocol")] _ip_addr = target.fsdb.get(prefix + ".ip_addr") _protocol = target.fsdb.get(prefix + ".protocol") _port = target.fsdb.get(prefix + ".port") _pid = target.fsdb.get(prefix + ".__id") _lport = prefix[len("interfaces.tunnel."):] if _ip_addr == ip_addr \ and _protocol == protocol \ and _port == port \ and commonl.process_alive(_pid, "/usr/bin/socat"): # there is already an active tunnel for this port # and it is alive, so use that return dict(result=int(_lport)) local_port = commonl.tcp_port_assigner( port_range=ttbl.config.tcp_port_range) ip_addr = ipaddress.ip_address(unicode(ip_addr)) if isinstance(ip_addr, ipaddress.IPv6Address): # beacause socat (and most others) likes it like that ip_addr = "[%s]" % ip_addr # this could be refactored using daemon_c, but it'd be # harder to follow the code and it is not really needed. p = subprocess.Popen([ "/usr/bin/socat", "-ly", "-lp", tunnel_id, "%s-LISTEN:%d,fork,reuseaddr" % (protocol, local_port), "%s:%s:%s" % (protocol, ip_addr, port) ], shell=False, cwd=target.state_dir, close_fds=True) pid = commonl.process_started(p.pid, "/usr/bin/socat", verification_f=commonl.tcp_port_busy, verification_f_args=(local_port, ), tag="socat-" + tunnel_id, log=target.log) if p.returncode != None: raise RuntimeError("TUNNEL %s: socat exited with %d" % (tunnel_id, p.returncode)) ttbl.daemon_pid_add(p.pid) # FIXME: race condition if it # died? target.fsdb.set("interfaces.tunnel.%s.__id" % local_port, p.pid) target.fsdb.set("interfaces.tunnel.%s.ip_addr" % local_port, str(ip_addr)) target.fsdb.set("interfaces.tunnel.%s.protocol" % local_port, protocol) target.fsdb.set("interfaces.tunnel.%s.port" % local_port, port) return dict(result=local_port)