blob: ee3e571e303dcd00b7e8d3014121fbaa1859acf8 [file] [log] [blame]
Yilin Yang19da6932019-12-10 13:39:28 +08001#!/usr/bin/env python3
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08002# Copyright 2015 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
Wei-Ning Huang7d029b12015-03-06 10:32:15 +08006import argparse
Yilin Yangf9fe1932019-11-04 17:09:34 +08007import binascii
8import codecs
Wei-Ning Huangb05cde32015-08-01 09:48:41 +08009import contextlib
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +080010import ctypes
11import ctypes.util
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080012import fcntl
Wei-Ning Huangb05cde32015-08-01 09:48:41 +080013import hashlib
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080014import json
15import logging
16import os
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +080017import platform
Yilin Yang8b7f5192020-01-08 11:43:00 +080018import queue
Wei-Ning Huang829e0c82015-05-26 14:37:23 +080019import re
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080020import select
Wei-Ning Huanga301f572015-06-03 17:34:21 +080021import signal
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080022import socket
Wei-Ning Huangf5311a02016-02-04 15:23:46 +080023import ssl
Wei-Ning Huangb05cde32015-08-01 09:48:41 +080024import struct
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080025import subprocess
26import sys
Moja Hsuc9ecc8b2015-07-13 11:39:17 +080027import termios
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080028import threading
29import time
Joel Kitching22b89042015-08-06 18:23:29 +080030import traceback
Wei-Ning Huang39169902015-09-19 06:00:23 +080031import tty
Yilin Yangf54fb912020-01-08 11:42:38 +080032import urllib.request
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080033import uuid
34
Wei-Ning Huang2132de32015-04-13 17:24:38 +080035import jsonrpclib
36from jsonrpclib.SimpleJSONRPCServer import SimpleJSONRPCServer
Wei-Ning Huang2132de32015-04-13 17:24:38 +080037
Yilin Yang64dd8592020-11-10 16:16:23 +080038from cros.factory.gooftool import cros_config as cros_config_module
Yilin Yangdc347592021-02-03 11:33:04 +080039from cros.factory.test import device_data
Yilin Yang64dd8592020-11-10 16:16:23 +080040from cros.factory.test import state
41from cros.factory.test.state import TestState
Yilin Yang1512d972020-11-19 13:34:42 +080042from cros.factory.test.test_lists import manager
Yilin Yang64dd8592020-11-10 16:16:23 +080043from cros.factory.utils import net_utils
Yilin Yang42ba5c62020-05-05 10:32:34 +080044from cros.factory.utils import process_utils
Cheng Yueh59a29662020-12-02 12:20:18 +080045from cros.factory.utils import sys_interface
Yilin Yang64dd8592020-11-10 16:16:23 +080046from cros.factory.utils import sys_utils
47from cros.factory.utils.type_utils import Enum
Yilin Yang42ba5c62020-05-05 10:32:34 +080048
Yilin Yangf54fb912020-01-08 11:42:38 +080049
Fei Shaobb0a3e62020-06-20 15:41:25 +080050_GHOST_RPC_PORT = int(os.getenv('GHOST_RPC_PORT', '4499'))
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080051
Fei Shaobb0a3e62020-06-20 15:41:25 +080052_OVERLORD_PORT = int(os.getenv('OVERLORD_PORT', '4455'))
53_OVERLORD_LAN_DISCOVERY_PORT = int(os.getenv('OVERLORD_LD_PORT', '4456'))
54_OVERLORD_HTTP_PORT = int(os.getenv('OVERLORD_HTTP_PORT', '9000'))
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080055
56_BUFSIZE = 8192
57_RETRY_INTERVAL = 2
Yilin Yang6b9ec9d2019-12-09 11:04:06 +080058_SEPARATOR = b'\r\n'
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080059_PING_TIMEOUT = 3
60_PING_INTERVAL = 5
61_REQUEST_TIMEOUT_SECS = 60
62_SHELL = os.getenv('SHELL', '/bin/bash')
Wei-Ning Huang7dbf4a72016-03-02 20:16:20 +080063_DEFAULT_BIND_ADDRESS = 'localhost'
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080064
Moja Hsuc9ecc8b2015-07-13 11:39:17 +080065_CONTROL_START = 128
66_CONTROL_END = 129
67
Wei-Ning Huanga301f572015-06-03 17:34:21 +080068_BLOCK_SIZE = 4096
Wei-Ning Huange0def6a2015-11-05 15:41:24 +080069_CONNECT_TIMEOUT = 3
Wei-Ning Huanga301f572015-06-03 17:34:21 +080070
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +080071# Stream control
72_STDIN_CLOSED = '##STDIN_CLOSED##'
73
Wei-Ning Huang7ec55342015-09-17 08:46:06 +080074SUCCESS = 'success'
75FAILED = 'failed'
76DISCONNECTED = 'disconnected'
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080077
Joel Kitching22b89042015-08-06 18:23:29 +080078
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080079class PingTimeoutError(Exception):
80 pass
81
82
83class RequestError(Exception):
84 pass
85
86
Fei Shaobd07c9a2020-06-15 19:04:50 +080087class BufferedSocket:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +080088 """A buffered socket that supports unrecv.
89
90 Allow putting back data back to the socket for the next recv() call.
91 """
Wei-Ning Huangf5311a02016-02-04 15:23:46 +080092 def __init__(self, sock):
93 self.sock = sock
Yilin Yang6b9ec9d2019-12-09 11:04:06 +080094 self._buf = b''
Wei-Ning Huanga28cd232016-01-27 15:04:41 +080095
Wei-Ning Huangf5311a02016-02-04 15:23:46 +080096 def fileno(self):
97 return self.sock.fileno()
98
Wei-Ning Huanga28cd232016-01-27 15:04:41 +080099 def Recv(self, bufsize, flags=0):
100 if self._buf:
101 if len(self._buf) >= bufsize:
102 ret = self._buf[:bufsize]
103 self._buf = self._buf[bufsize:]
104 return ret
Yilin Yang15a3f8f2020-01-03 17:49:00 +0800105 ret = self._buf
Yilin Yang6b9ec9d2019-12-09 11:04:06 +0800106 self._buf = b''
Yilin Yang15a3f8f2020-01-03 17:49:00 +0800107 return ret + self.sock.recv(bufsize - len(ret), flags)
108 return self.sock.recv(bufsize, flags)
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800109
110 def UnRecv(self, buf):
111 self._buf = buf + self._buf
112
113 def Send(self, *args, **kwargs):
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800114 return self.sock.send(*args, **kwargs)
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800115
116 def RecvBuf(self):
117 """Only recive from buffer."""
118 ret = self._buf
Yilin Yang6b9ec9d2019-12-09 11:04:06 +0800119 self._buf = b''
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800120 return ret
121
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800122 def Close(self):
123 self.sock.close()
124
125
Fei Shaobd07c9a2020-06-15 19:04:50 +0800126class TLSSettings:
Wei-Ning Huang47c79b82016-05-24 01:24:46 +0800127 def __init__(self, tls_cert_file, verify):
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800128 """Constructor.
129
130 Args:
131 tls_cert_file: TLS certificate in PEM format.
132 enable_tls_without_verify: enable TLS but don't verify certificate.
133 """
Wei-Ning Huang47c79b82016-05-24 01:24:46 +0800134 self._enabled = False
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800135 self._tls_cert_file = tls_cert_file
Wei-Ning Huang47c79b82016-05-24 01:24:46 +0800136 self._verify = verify
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800137 self._tls_context = None
138
Wei-Ning Huang47c79b82016-05-24 01:24:46 +0800139 def _UpdateContext(self):
140 if not self._enabled:
141 self._tls_context = None
142 return
143
144 self._tls_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
145 self._tls_context.verify_mode = ssl.CERT_REQUIRED
146
147 if self._verify:
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800148 if self._tls_cert_file:
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800149 self._tls_context.check_hostname = True
150 try:
151 self._tls_context.load_verify_locations(self._tls_cert_file)
Wei-Ning Huang47c79b82016-05-24 01:24:46 +0800152 logging.info('TLSSettings: using user-supplied ca-certificate')
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800153 except IOError as e:
154 logging.error('TLSSettings: %s: %s', self._tls_cert_file, e)
155 sys.exit(1)
Wei-Ning Huang47c79b82016-05-24 01:24:46 +0800156 else:
157 self._tls_context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
158 logging.info('TLSSettings: using built-in ca-certificates')
159 else:
160 self._tls_context.verify_mode = ssl.CERT_NONE
161 logging.info('TLSSettings: skipping TLS verification!!!')
162
163 def SetEnabled(self, enabled):
164 logging.info('TLSSettings: enabled: %s', enabled)
165
166 if self._enabled != enabled:
167 self._enabled = enabled
168 self._UpdateContext()
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800169
170 def Enabled(self):
Wei-Ning Huang47c79b82016-05-24 01:24:46 +0800171 return self._enabled
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800172
173 def Context(self):
174 return self._tls_context
175
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800176
Fei Shaobd07c9a2020-06-15 19:04:50 +0800177class Ghost:
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800178 """Ghost implements the client protocol of Overlord.
179
180 Ghost provide terminal/shell/logcat functionality and manages the client
181 side connectivity.
182 """
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800183 NONE, AGENT, TERMINAL, SHELL, LOGCAT, FILE, FORWARD = range(7)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800184
185 MODE_NAME = {
186 NONE: 'NONE',
187 AGENT: 'Agent',
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +0800188 TERMINAL: 'Terminal',
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800189 SHELL: 'Shell',
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800190 LOGCAT: 'Logcat',
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800191 FILE: 'File',
192 FORWARD: 'Forward'
Peter Shihe6afab32018-09-11 17:16:48 +0800193 }
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800194
Wei-Ning Huangc9c97f02015-05-19 15:05:42 +0800195 RANDOM_MID = '##random_mid##'
196
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800197 def __init__(self, overlord_addrs, tls_settings=None, mode=AGENT, mid=None,
198 sid=None, prop_file=None, terminal_sid=None, tty_device=None,
Yilin Yang77668412021-02-02 10:53:36 +0800199 command=None, file_op=None, port=None, tls_mode=None,
200 ovl_path=None, certificate_dir=None):
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800201 """Constructor.
202
203 Args:
204 overlord_addrs: a list of possible address of overlord.
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800205 tls_settings: a TLSSetting object.
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800206 mode: client mode, either AGENT, SHELL or LOGCAT
Wei-Ning Huangc9c97f02015-05-19 15:05:42 +0800207 mid: a str to set for machine ID. If mid equals Ghost.RANDOM_MID, machine
208 id is randomly generated.
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800209 sid: session ID. If the connection is requested by overlord, sid should
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800210 be set to the corresponding session id assigned by overlord.
Wei-Ning Huang23ed0162015-09-18 14:42:03 +0800211 prop_file: properties file filename.
Wei-Ning Huangd521f282015-08-07 05:28:04 +0800212 terminal_sid: the terminal session ID associate with this client. This is
213 use for file download.
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800214 tty_device: the terminal device to open, if tty_device is None, as pseudo
215 terminal will be opened instead.
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800216 command: the command to execute when we are in SHELL mode.
Wei-Ning Huang8ee3bcd2015-10-01 17:10:01 +0800217 file_op: a tuple (action, filepath, perm). action is either 'download' or
218 'upload'. perm is the permission to set for the file.
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800219 port: port number to forward.
Peter Shih220a96d2016-12-22 17:02:16 +0800220 tls_mode: can be [True, False, None]. if not None, skip detection of
221 TLS and assume whether server use TLS or not.
Yilin Yang77668412021-02-02 10:53:36 +0800222 ovl_path: path to ovl tool.
223 certificate_dir: path to overlord certificate directory
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800224 """
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800225 assert mode in [Ghost.AGENT, Ghost.TERMINAL, Ghost.SHELL, Ghost.FILE,
226 Ghost.FORWARD]
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +0800227 if mode == Ghost.SHELL:
228 assert command is not None
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800229 if mode == Ghost.FILE:
230 assert file_op is not None
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800231
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800232 self._platform = platform.system()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800233 self._overlord_addrs = overlord_addrs
Wei-Ning Huangad330c52015-03-12 20:34:18 +0800234 self._connected_addr = None
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800235 self._tls_settings = tls_settings
Wei-Ning Huangc9c97f02015-05-19 15:05:42 +0800236 self._mid = mid
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800237 self._sock = None
Wei-Ning Huang7ec55342015-09-17 08:46:06 +0800238 self._mode = mode
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800239 self._machine_id = self.GetMachineID()
Wei-Ning Huangfed95862015-08-07 03:17:11 +0800240 self._session_id = sid if sid is not None else str(uuid.uuid4())
Wei-Ning Huangd521f282015-08-07 05:28:04 +0800241 self._terminal_session_id = terminal_sid
Wei-Ning Huang23ed0162015-09-18 14:42:03 +0800242 self._ttyname_to_sid = {}
243 self._terminal_sid_to_pid = {}
244 self._prop_file = prop_file
Wei-Ning Huang7d029b12015-03-06 10:32:15 +0800245 self._properties = {}
Wei-Ning Huang7ec55342015-09-17 08:46:06 +0800246 self._register_status = DISCONNECTED
247 self._reset = threading.Event()
Peter Shih220a96d2016-12-22 17:02:16 +0800248 self._tls_mode = tls_mode
Yilin Yang77668412021-02-02 10:53:36 +0800249 self._ovl_path = ovl_path
250 self._certificate_dir = certificate_dir
Wei-Ning Huang7ec55342015-09-17 08:46:06 +0800251
Yilin Yang89a136c2021-01-05 15:25:05 +0800252 # The information of track_connection is lost after ghost restart.
253 self._track_connection = None
254 self._track_connection_timeout_secs = 900
255
Wei-Ning Huang7ec55342015-09-17 08:46:06 +0800256 # RPC
Wei-Ning Huang7ec55342015-09-17 08:46:06 +0800257 self._requests = {}
Yilin Yang8b7f5192020-01-08 11:43:00 +0800258 self._queue = queue.Queue()
Wei-Ning Huang7ec55342015-09-17 08:46:06 +0800259
260 # Protocol specific
261 self._last_ping = 0
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800262 self._tty_device = tty_device
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +0800263 self._shell_command = command
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800264 self._file_op = file_op
Yilin Yang8b7f5192020-01-08 11:43:00 +0800265 self._download_queue = queue.Queue()
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800266 self._port = port
Wei-Ning Huang7ec55342015-09-17 08:46:06 +0800267
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800268 def SetIgnoreChild(self, status):
269 # Only ignore child for Agent since only it could spawn child Ghost.
270 if self._mode == Ghost.AGENT:
271 signal.signal(signal.SIGCHLD,
272 signal.SIG_IGN if status else signal.SIG_DFL)
273
274 def GetFileSha1(self, filename):
Yilin Yang0412c272019-12-05 16:57:40 +0800275 with open(filename, 'rb') as f:
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800276 return hashlib.sha1(f.read()).hexdigest()
277
Wei-Ning Huang47c79b82016-05-24 01:24:46 +0800278 def TLSEnabled(self, host, port):
279 """Determine if TLS is enabled on given server address."""
Wei-Ning Huang58833882015-09-16 16:52:37 +0800280 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
281 try:
Wei-Ning Huang47c79b82016-05-24 01:24:46 +0800282 # Allow any certificate since we only want to check if server talks TLS.
283 context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
284 context.verify_mode = ssl.CERT_NONE
Wei-Ning Huang58833882015-09-16 16:52:37 +0800285
Wei-Ning Huang47c79b82016-05-24 01:24:46 +0800286 sock = context.wrap_socket(sock, server_hostname=host)
287 sock.settimeout(_CONNECT_TIMEOUT)
288 sock.connect((host, port))
289 return True
Wei-Ning Huangb6605d22016-06-22 17:33:37 +0800290 except ssl.SSLError:
291 return False
Stimim Chen3899a912020-07-17 10:52:03 +0800292 except socket.timeout:
293 return False
Wei-Ning Huang47c79b82016-05-24 01:24:46 +0800294 except socket.error: # Connect refused or timeout
295 raise
Wei-Ning Huang58833882015-09-16 16:52:37 +0800296 except Exception:
Wei-Ning Huang47c79b82016-05-24 01:24:46 +0800297 return False # For whatever reason above failed, assume False
Wei-Ning Huang58833882015-09-16 16:52:37 +0800298
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800299 def Upgrade(self):
300 logging.info('Upgrade: initiating upgrade sequence...')
301
Wei-Ning Huang47c79b82016-05-24 01:24:46 +0800302 try:
Wei-Ning Huangb6605d22016-06-22 17:33:37 +0800303 https_enabled = self.TLSEnabled(self._connected_addr[0],
304 _OVERLORD_HTTP_PORT)
Wei-Ning Huang47c79b82016-05-24 01:24:46 +0800305 except socket.error:
Wei-Ning Huangb6605d22016-06-22 17:33:37 +0800306 logging.error('Upgrade: failed to connect to Overlord HTTP server, '
307 'abort')
308 return
Wei-Ning Huang47c79b82016-05-24 01:24:46 +0800309
310 if self._tls_settings.Enabled() and not https_enabled:
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800311 logging.error('Upgrade: TLS enforced but found Overlord HTTP server '
312 'without TLS enabled! Possible mis-configuration or '
313 'DNS/IP spoofing detected, abort')
314 return
315
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800316 scriptpath = os.path.abspath(sys.argv[0])
Wei-Ning Huang03f9f762015-09-16 21:51:35 +0800317 url = 'http%s://%s:%d/upgrade/ghost.py' % (
Wei-Ning Huang47c79b82016-05-24 01:24:46 +0800318 's' if https_enabled else '', self._connected_addr[0],
Wei-Ning Huang03f9f762015-09-16 21:51:35 +0800319 _OVERLORD_HTTP_PORT)
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800320
321 # Download sha1sum for ghost.py for verification
322 try:
Wei-Ning Huange0def6a2015-11-05 15:41:24 +0800323 with contextlib.closing(
Yilin Yangf54fb912020-01-08 11:42:38 +0800324 urllib.request.urlopen(url + '.sha1', timeout=_CONNECT_TIMEOUT,
325 context=self._tls_settings.Context())) as f:
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800326 if f.getcode() != 200:
327 raise RuntimeError('HTTP status %d' % f.getcode())
328 sha1sum = f.read().strip()
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800329 except (ssl.SSLError, ssl.CertificateError) as e:
330 logging.error('Upgrade: %s: %s', e.__class__.__name__, e)
331 return
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800332 except Exception:
333 logging.error('Upgrade: failed to download sha1sum file, abort')
334 return
335
336 if self.GetFileSha1(scriptpath) == sha1sum:
337 logging.info('Upgrade: ghost is already up-to-date, skipping upgrade')
338 return
339
340 # Download upgrade version of ghost.py
341 try:
Wei-Ning Huange0def6a2015-11-05 15:41:24 +0800342 with contextlib.closing(
Yilin Yangf54fb912020-01-08 11:42:38 +0800343 urllib.request.urlopen(url, timeout=_CONNECT_TIMEOUT,
344 context=self._tls_settings.Context())) as f:
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800345 if f.getcode() != 200:
346 raise RuntimeError('HTTP status %d' % f.getcode())
347 data = f.read()
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800348 except (ssl.SSLError, ssl.CertificateError) as e:
349 logging.error('Upgrade: %s: %s', e.__class__.__name__, e)
350 return
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800351 except Exception:
352 logging.error('Upgrade: failed to download upgrade, abort')
353 return
354
355 # Compare SHA1 sum
356 if hashlib.sha1(data).hexdigest() != sha1sum:
357 logging.error('Upgrade: sha1sum mismatch, abort')
358 return
359
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800360 try:
Yilin Yang235e5982019-12-26 10:36:22 +0800361 with open(scriptpath, 'wb') as f:
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800362 f.write(data)
363 except Exception:
364 logging.error('Upgrade: failed to write upgrade onto disk, abort')
365 return
366
367 logging.info('Upgrade: restarting ghost...')
368 self.CloseSockets()
369 self.SetIgnoreChild(False)
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +0800370 os.execve(scriptpath, [scriptpath] + sys.argv[1:], os.environ)
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800371
Wei-Ning Huang23ed0162015-09-18 14:42:03 +0800372 def LoadProperties(self):
Wei-Ning Huang7d029b12015-03-06 10:32:15 +0800373 try:
Wei-Ning Huang23ed0162015-09-18 14:42:03 +0800374 if self._prop_file:
375 with open(self._prop_file, 'r') as f:
376 self._properties = json.loads(f.read())
Yilin Yang77668412021-02-02 10:53:36 +0800377 if self._ovl_path:
378 self._properties['ovl_path'] = self._ovl_path
379 if self._certificate_dir:
380 self._properties['certificate_dir'] = self._certificate_dir
Wei-Ning Huang7d029b12015-03-06 10:32:15 +0800381 except Exception as e:
Peter Shih769b0772018-02-26 14:44:28 +0800382 logging.error('LoadProperties: %s', e)
Wei-Ning Huang7d029b12015-03-06 10:32:15 +0800383
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800384 def CloseSockets(self):
385 # Close sockets opened by parent process, since we don't use it anymore.
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800386 if self._platform == 'Linux':
387 for fd in os.listdir('/proc/self/fd/'):
388 try:
389 real_fd = os.readlink('/proc/self/fd/%s' % fd)
390 if real_fd.startswith('socket'):
391 os.close(int(fd))
392 except Exception:
393 pass
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800394
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800395 def SpawnGhost(self, mode, sid=None, terminal_sid=None, tty_device=None,
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800396 command=None, file_op=None, port=None):
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800397 """Spawn a child ghost with specific mode.
398
399 Returns:
400 The spawned child process pid.
401 """
Joel Kitching22b89042015-08-06 18:23:29 +0800402 # Restore the default signal handler, so our child won't have problems.
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800403 self.SetIgnoreChild(False)
404
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800405 pid = os.fork()
406 if pid == 0:
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800407 self.CloseSockets()
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800408 g = Ghost([self._connected_addr], tls_settings=self._tls_settings,
409 mode=mode, mid=Ghost.RANDOM_MID, sid=sid,
Wei-Ning Huang23ed0162015-09-18 14:42:03 +0800410 terminal_sid=terminal_sid, tty_device=tty_device,
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800411 command=command, file_op=file_op, port=port)
Wei-Ning Huang2132de32015-04-13 17:24:38 +0800412 g.Start()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800413 sys.exit(0)
414 else:
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800415 self.SetIgnoreChild(True)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800416 return pid
417
418 def Timestamp(self):
419 return int(time.time())
420
421 def GetGateWayIP(self):
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800422 if self._platform == 'Darwin':
Yilin Yang42ba5c62020-05-05 10:32:34 +0800423 output = process_utils.CheckOutput(['route', '-n', 'get', 'default'])
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800424 ret = re.search('gateway: (.*)', output)
425 if ret:
426 return [ret.group(1)]
Peter Shiha78867d2018-02-26 14:17:51 +0800427 return []
Fei Shao12ecf382020-06-23 18:32:26 +0800428 if self._platform == 'Linux':
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800429 with open('/proc/net/route', 'r') as f:
430 lines = f.readlines()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800431
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800432 ips = []
433 for line in lines:
434 parts = line.split('\t')
435 if parts[2] == '00000000':
436 continue
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800437
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800438 try:
Yilin Yangf9fe1932019-11-04 17:09:34 +0800439 h = codecs.decode(parts[2], 'hex')
Yilin Yangacd3c792020-05-05 10:00:30 +0800440 ips.append('.'.join([str(x) for x in reversed(h)]))
Yilin Yangf9fe1932019-11-04 17:09:34 +0800441 except (TypeError, binascii.Error):
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800442 pass
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800443
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800444 return ips
Fei Shao12ecf382020-06-23 18:32:26 +0800445
446 logging.warning('GetGateWayIP: unsupported platform')
447 return []
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800448
Hung-Te Lin41ff8f32017-08-30 08:10:39 +0800449 def GetFactoryServerIP(self):
Wei-Ning Huang829e0c82015-05-26 14:37:23 +0800450 try:
Hung-Te Lin41ff8f32017-08-30 08:10:39 +0800451 from cros.factory.test import server_proxy
Wei-Ning Huang829e0c82015-05-26 14:37:23 +0800452
Hung-Te Lin41ff8f32017-08-30 08:10:39 +0800453 url = server_proxy.GetServerURL()
Wei-Ning Huang829e0c82015-05-26 14:37:23 +0800454 match = re.match(r'^https?://(.*):.*$', url)
455 if match:
456 return [match.group(1)]
457 except Exception:
458 pass
459 return []
460
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800461 def GetMachineID(self):
462 """Generates machine-dependent ID string for a machine.
463 There are many ways to generate a machine ID:
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800464 Linux:
465 1. factory device_id
Peter Shih5f1f48c2017-06-26 14:12:00 +0800466 2. /sys/class/dmi/id/product_uuid (only available on intel machines)
467 3. MAC address
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800468 We follow the listed order to generate machine ID, and fallback to the
469 next alternative if the previous doesn't work.
470
471 Darwin:
472 All Darwin system should have the IOPlatformSerialNumber attribute.
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800473 """
Wei-Ning Huangc9c97f02015-05-19 15:05:42 +0800474 if self._mid == Ghost.RANDOM_MID:
Wei-Ning Huangaed90452015-03-23 17:50:21 +0800475 return str(uuid.uuid4())
Fei Shao12ecf382020-06-23 18:32:26 +0800476 if self._mid:
Wei-Ning Huangc9c97f02015-05-19 15:05:42 +0800477 return self._mid
Wei-Ning Huangaed90452015-03-23 17:50:21 +0800478
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800479 # Darwin
480 if self._platform == 'Darwin':
Yilin Yang42ba5c62020-05-05 10:32:34 +0800481 output = process_utils.CheckOutput(['ioreg', '-rd1', '-c',
482 'IOPlatformExpertDevice'])
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800483 ret = re.search('"IOPlatformSerialNumber" = "(.*)"', output)
484 if ret:
485 return ret.group(1)
486
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800487 # Try factory device id
488 try:
Hung-Te Linda8eb992017-09-28 03:27:12 +0800489 from cros.factory.test import session
490 return session.GetDeviceID()
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800491 except Exception:
492 pass
493
Wei-Ning Huang1d7603b2015-07-03 17:38:56 +0800494 # Try DMI product UUID
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800495 try:
496 with open('/sys/class/dmi/id/product_uuid', 'r') as f:
497 return f.read().strip()
498 except Exception:
499 pass
500
Wei-Ning Huang1d7603b2015-07-03 17:38:56 +0800501 # Use MAC address if non is available
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800502 try:
503 macs = []
504 ifaces = sorted(os.listdir('/sys/class/net'))
505 for iface in ifaces:
506 if iface == 'lo':
507 continue
508
509 with open('/sys/class/net/%s/address' % iface, 'r') as f:
510 macs.append(f.read().strip())
511
512 return ';'.join(macs)
513 except Exception:
514 pass
515
Peter Shihcb0e5512017-06-14 16:59:46 +0800516 raise RuntimeError("can't generate machine ID")
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800517
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800518 def GetProcessWorkingDirectory(self, pid):
519 if self._platform == 'Linux':
520 return os.readlink('/proc/%d/cwd' % pid)
Fei Shao12ecf382020-06-23 18:32:26 +0800521 if self._platform == 'Darwin':
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800522 PROC_PIDVNODEPATHINFO = 9
523 proc_vnodepathinfo_size = 2352
524 vid_path_offset = 152
525
526 proc = ctypes.cdll.LoadLibrary(ctypes.util.find_library('libproc'))
527 buf = ctypes.create_string_buffer('\0' * proc_vnodepathinfo_size)
528 proc.proc_pidinfo(pid, PROC_PIDVNODEPATHINFO, 0,
529 ctypes.byref(buf), proc_vnodepathinfo_size)
530 buf = buf.raw[vid_path_offset:]
531 n = buf.index('\0')
532 return buf[:n]
Fei Shao12ecf382020-06-23 18:32:26 +0800533 raise RuntimeError('GetProcessWorkingDirectory: unsupported platform')
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800534
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800535 def Reset(self):
536 """Reset state and clear request handlers."""
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800537 if self._sock is not None:
538 self._sock.Close()
539 self._sock = None
Wei-Ning Huang2132de32015-04-13 17:24:38 +0800540 self._reset.clear()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800541 self._last_ping = 0
542 self._requests = {}
Wei-Ning Huang23ed0162015-09-18 14:42:03 +0800543 self.LoadProperties()
Wei-Ning Huang7ec55342015-09-17 08:46:06 +0800544 self._register_status = DISCONNECTED
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800545
546 def SendMessage(self, msg):
547 """Serialize the message and send it through the socket."""
Yilin Yang6b9ec9d2019-12-09 11:04:06 +0800548 self._sock.Send(json.dumps(msg).encode('utf-8') + _SEPARATOR)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800549
550 def SendRequest(self, name, args, handler=None,
551 timeout=_REQUEST_TIMEOUT_SECS):
552 if handler and not callable(handler):
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800553 raise RequestError('Invalid request handler for msg "%s"' % name)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800554
555 rid = str(uuid.uuid4())
556 msg = {'rid': rid, 'timeout': timeout, 'name': name, 'params': args}
Wei-Ning Huange2981862015-08-03 15:03:08 +0800557 if timeout >= 0:
558 self._requests[rid] = [self.Timestamp(), timeout, handler]
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800559 self.SendMessage(msg)
560
561 def SendResponse(self, omsg, status, params=None):
562 msg = {'rid': omsg['rid'], 'response': status, 'params': params}
563 self.SendMessage(msg)
564
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800565 def HandleTTYControl(self, fd, control_str):
566 msg = json.loads(control_str)
Moja Hsuc9ecc8b2015-07-13 11:39:17 +0800567 command = msg['command']
568 params = msg['params']
569 if command == 'resize':
570 # some error happened on websocket
571 if len(params) != 2:
572 return
573 winsize = struct.pack('HHHH', params[0], params[1], 0, 0)
574 fcntl.ioctl(fd, termios.TIOCSWINSZ, winsize)
575 else:
Yilin Yang9881b1e2019-12-11 11:47:33 +0800576 logging.warning('Invalid request command "%s"', command)
Moja Hsuc9ecc8b2015-07-13 11:39:17 +0800577
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800578 def SpawnTTYServer(self, unused_var):
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800579 """Spawn a TTY server and forward I/O to the TCP socket."""
580 logging.info('SpawnTTYServer: started')
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800581
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800582 try:
583 if self._tty_device is None:
584 pid, fd = os.forkpty()
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800585
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800586 if pid == 0:
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800587 ttyname = os.ttyname(sys.stdout.fileno())
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800588 try:
589 server = GhostRPCServer()
590 server.RegisterTTY(self._session_id, ttyname)
591 server.RegisterSession(self._session_id, os.getpid())
592 except Exception:
593 # If ghost is launched without RPC server, the call will fail but we
594 # can ignore it.
595 pass
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800596
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800597 # The directory that contains the current running ghost script
598 script_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800599
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800600 env = os.environ.copy()
601 env['USER'] = os.getenv('USER', 'root')
602 env['HOME'] = os.getenv('HOME', '/root')
603 env['PATH'] = os.getenv('PATH') + ':%s' % script_dir
604 os.chdir(env['HOME'])
605 os.execve(_SHELL, [_SHELL], env)
606 else:
607 fd = os.open(self._tty_device, os.O_RDWR)
Wei-Ning Huang39169902015-09-19 06:00:23 +0800608 tty.setraw(fd)
Stimim Chen3899a912020-07-17 10:52:03 +0800609 # 0: iflag
610 # 1: oflag
611 # 2: cflag
612 # 3: lflag
613 # 4: ispeed
614 # 5: ospeed
615 # 6: cc
Wei-Ning Huang39169902015-09-19 06:00:23 +0800616 attr = termios.tcgetattr(fd)
Stimim Chen3899a912020-07-17 10:52:03 +0800617 attr[0] &= (termios.IXON | termios.IXOFF)
Wei-Ning Huang39169902015-09-19 06:00:23 +0800618 attr[2] |= termios.CLOCAL
619 attr[2] &= ~termios.CRTSCTS
620 attr[4] = termios.B115200
621 attr[5] = termios.B115200
622 termios.tcsetattr(fd, termios.TCSANOW, attr)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800623
Stimim Chen3899a912020-07-17 10:52:03 +0800624 nonlocals = {
625 'control_state': None,
626 'control_str': b''
627 }
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800628
629 def _ProcessBuffer(buf):
Stimim Chen3899a912020-07-17 10:52:03 +0800630 write_buffer = b''
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800631 while buf:
632 if nonlocals['control_state']:
Stimim Chen3899a912020-07-17 10:52:03 +0800633 if _CONTROL_END in buf:
634 index = buf.index(_CONTROL_END)
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800635 nonlocals['control_str'] += buf[:index]
636 self.HandleTTYControl(fd, nonlocals['control_str'])
637 nonlocals['control_state'] = None
Stimim Chen3899a912020-07-17 10:52:03 +0800638 nonlocals['control_str'] = b''
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800639 buf = buf[index+1:]
640 else:
641 nonlocals['control_str'] += buf
Stimim Chen3899a912020-07-17 10:52:03 +0800642 buf = b''
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800643 else:
Stimim Chen3899a912020-07-17 10:52:03 +0800644 if _CONTROL_START in buf:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800645 nonlocals['control_state'] = _CONTROL_START
Stimim Chen3899a912020-07-17 10:52:03 +0800646 index = buf.index(_CONTROL_START)
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800647 write_buffer += buf[:index]
648 buf = buf[index+1:]
649 else:
650 write_buffer += buf
Stimim Chen3899a912020-07-17 10:52:03 +0800651 buf = b''
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800652
653 if write_buffer:
654 os.write(fd, write_buffer)
655
656 _ProcessBuffer(self._sock.RecvBuf())
657
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800658 while True:
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800659 rd, unused_wd, unused_xd = select.select([self._sock, fd], [], [])
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800660
661 if fd in rd:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800662 self._sock.Send(os.read(fd, _BUFSIZE))
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800663
664 if self._sock in rd:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800665 buf = self._sock.Recv(_BUFSIZE)
Peter Shihaacbc2f2017-06-16 14:39:29 +0800666 if not buf:
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +0800667 raise RuntimeError('connection terminated')
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800668 _ProcessBuffer(buf)
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800669 except Exception as e:
Stimim Chen3899a912020-07-17 10:52:03 +0800670 logging.error('SpawnTTYServer: %s', e, exc_info=True)
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800671 finally:
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800672 self._sock.Close()
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800673
674 logging.info('SpawnTTYServer: terminated')
Yilin Yanga03fd0a2020-10-23 16:58:14 +0800675 os._exit(0) # pylint: disable=protected-access
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800676
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800677 def SpawnShellServer(self, unused_var):
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +0800678 """Spawn a shell server and forward input/output from/to the TCP socket."""
679 logging.info('SpawnShellServer: started')
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800680
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +0800681 # Add ghost executable to PATH
682 script_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
683 env = os.environ.copy()
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800684 env['PATH'] = '%s:%s' % (script_dir, os.getenv('PATH'))
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +0800685
686 # Execute shell command from HOME directory
687 os.chdir(os.getenv('HOME', '/tmp'))
688
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +0800689 p = subprocess.Popen(self._shell_command, stdin=subprocess.PIPE,
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800690 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +0800691 shell=True, env=env)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800692
693 def make_non_block(fd):
694 fl = fcntl.fcntl(fd, fcntl.F_GETFL)
695 fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
696
697 make_non_block(p.stdout)
698 make_non_block(p.stderr)
699
700 try:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800701 p.stdin.write(self._sock.RecvBuf())
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800702
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800703 while True:
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800704 rd, unused_wd, unused_xd = select.select(
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800705 [p.stdout, p.stderr, self._sock], [], [])
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800706 if p.stdout in rd:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800707 self._sock.Send(p.stdout.read(_BUFSIZE))
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800708
709 if p.stderr in rd:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800710 self._sock.Send(p.stderr.read(_BUFSIZE))
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800711
712 if self._sock in rd:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800713 ret = self._sock.Recv(_BUFSIZE)
Peter Shihaacbc2f2017-06-16 14:39:29 +0800714 if not ret:
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +0800715 raise RuntimeError('connection terminated')
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800716
717 try:
718 idx = ret.index(_STDIN_CLOSED * 2)
719 p.stdin.write(ret[:idx])
720 p.stdin.close()
721 except ValueError:
722 p.stdin.write(ret)
Wei-Ning Huangf14c84e2015-08-03 15:03:08 +0800723 p.poll()
Peter Shihe6afab32018-09-11 17:16:48 +0800724 if p.returncode is not None:
Wei-Ning Huangf14c84e2015-08-03 15:03:08 +0800725 break
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800726 except Exception as e:
Stimim Chen3899a912020-07-17 10:52:03 +0800727 logging.error('SpawnShellServer: %s', e, exc_info=True)
Wei-Ning Huangf14c84e2015-08-03 15:03:08 +0800728 finally:
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800729 # Check if the process is terminated. If not, Send SIGTERM to process,
730 # then wait for 1 second. Send another SIGKILL to make sure the process is
731 # terminated.
732 p.poll()
733 if p.returncode is None:
734 try:
735 p.terminate()
736 time.sleep(1)
737 p.kill()
738 except Exception:
739 pass
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +0800740
741 p.wait()
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800742 self._sock.Close()
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800743
744 logging.info('SpawnShellServer: terminated')
Yilin Yanga03fd0a2020-10-23 16:58:14 +0800745 os._exit(0) # pylint: disable=protected-access
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800746
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800747 def InitiateFileOperation(self, unused_var):
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800748 if self._file_op[0] == 'download':
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800749 try:
750 size = os.stat(self._file_op[1]).st_size
751 except OSError as e:
752 logging.error('InitiateFileOperation: download: %s', e)
753 sys.exit(1)
754
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800755 self.SendRequest('request_to_download',
Wei-Ning Huangd521f282015-08-07 05:28:04 +0800756 {'terminal_sid': self._terminal_session_id,
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800757 'filename': os.path.basename(self._file_op[1]),
758 'size': size})
Wei-Ning Huange2981862015-08-03 15:03:08 +0800759 elif self._file_op[0] == 'upload':
760 self.SendRequest('clear_to_upload', {}, timeout=-1)
761 self.StartUploadServer()
762 else:
763 logging.error('InitiateFileOperation: unknown file operation, ignored')
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800764
765 def StartDownloadServer(self):
766 logging.info('StartDownloadServer: started')
767
768 try:
769 with open(self._file_op[1], 'rb') as f:
770 while True:
771 data = f.read(_BLOCK_SIZE)
Peter Shihaacbc2f2017-06-16 14:39:29 +0800772 if not data:
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800773 break
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800774 self._sock.Send(data)
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800775 except Exception as e:
776 logging.error('StartDownloadServer: %s', e)
777 finally:
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800778 self._sock.Close()
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800779
780 logging.info('StartDownloadServer: terminated')
Yilin Yangb5030952021-01-19 12:44:32 +0800781 os._exit(0) # pylint: disable=protected-access
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800782
Wei-Ning Huange2981862015-08-03 15:03:08 +0800783 def StartUploadServer(self):
784 logging.info('StartUploadServer: started')
Wei-Ning Huange2981862015-08-03 15:03:08 +0800785 try:
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800786 filepath = self._file_op[1]
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800787 dirname = os.path.dirname(filepath)
788 if not os.path.exists(dirname):
789 try:
790 os.makedirs(dirname)
791 except Exception:
792 pass
Wei-Ning Huange2981862015-08-03 15:03:08 +0800793
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800794 with open(filepath, 'wb') as f:
Wei-Ning Huang8ee3bcd2015-10-01 17:10:01 +0800795 if self._file_op[2]:
796 os.fchmod(f.fileno(), self._file_op[2])
797
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800798 f.write(self._sock.RecvBuf())
799
Wei-Ning Huange2981862015-08-03 15:03:08 +0800800 while True:
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800801 rd, unused_wd, unused_xd = select.select([self._sock], [], [])
Wei-Ning Huange2981862015-08-03 15:03:08 +0800802 if self._sock in rd:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800803 buf = self._sock.Recv(_BLOCK_SIZE)
Peter Shihaacbc2f2017-06-16 14:39:29 +0800804 if not buf:
Wei-Ning Huange2981862015-08-03 15:03:08 +0800805 break
806 f.write(buf)
807 except socket.error as e:
808 logging.error('StartUploadServer: socket error: %s', e)
809 except Exception as e:
810 logging.error('StartUploadServer: %s', e)
811 finally:
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800812 self._sock.Close()
Wei-Ning Huange2981862015-08-03 15:03:08 +0800813
814 logging.info('StartUploadServer: terminated')
Yilin Yanga03fd0a2020-10-23 16:58:14 +0800815 os._exit(0) # pylint: disable=protected-access
Wei-Ning Huange2981862015-08-03 15:03:08 +0800816
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800817 def SpawnPortForwardServer(self, unused_var):
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800818 """Spawn a port forwarding server and forward I/O to the TCP socket."""
819 logging.info('SpawnPortForwardServer: started')
820
821 src_sock = None
822 try:
823 src_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Wei-Ning Huange0def6a2015-11-05 15:41:24 +0800824 src_sock.settimeout(_CONNECT_TIMEOUT)
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800825 src_sock.connect(('localhost', self._port))
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800826
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800827 src_sock.send(self._sock.RecvBuf())
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800828
829 while True:
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800830 rd, unused_wd, unused_xd = select.select([self._sock, src_sock], [], [])
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800831
832 if self._sock in rd:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800833 data = self._sock.Recv(_BUFSIZE)
Peter Shihaacbc2f2017-06-16 14:39:29 +0800834 if not data:
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +0800835 raise RuntimeError('connection terminated')
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800836 src_sock.send(data)
837
838 if src_sock in rd:
839 data = src_sock.recv(_BUFSIZE)
Peter Shihaacbc2f2017-06-16 14:39:29 +0800840 if not data:
Yilin Yang969b8012020-12-16 14:38:00 +0800841 continue
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800842 self._sock.Send(data)
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800843 except Exception as e:
844 logging.error('SpawnPortForwardServer: %s', e)
845 finally:
846 if src_sock:
847 src_sock.close()
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800848 self._sock.Close()
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800849
850 logging.info('SpawnPortForwardServer: terminated')
Yilin Yanga03fd0a2020-10-23 16:58:14 +0800851 os._exit(0) # pylint: disable=protected-access
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800852
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800853 def Ping(self):
854 def timeout_handler(x):
855 if x is None:
856 raise PingTimeoutError
857
858 self._last_ping = self.Timestamp()
859 self.SendRequest('ping', {}, timeout_handler, 5)
860
Wei-Ning Huangae923642015-09-24 14:08:09 +0800861 def HandleFileDownloadRequest(self, msg):
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800862 params = msg['params']
Wei-Ning Huangae923642015-09-24 14:08:09 +0800863 filepath = params['filename']
864 if not os.path.isabs(filepath):
865 filepath = os.path.join(os.getenv('HOME', '/tmp'), filepath)
866
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800867 try:
Wei-Ning Huang11c35022015-10-21 16:52:32 +0800868 with open(filepath, 'r') as _:
Wei-Ning Huang46a3fc92015-10-06 02:35:27 +0800869 pass
870 except Exception as e:
Peter Shiha78867d2018-02-26 14:17:51 +0800871 self.SendResponse(msg, str(e))
872 return
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800873
874 self.SpawnGhost(self.FILE, params['sid'],
Wei-Ning Huangae923642015-09-24 14:08:09 +0800875 file_op=('download', filepath))
876 self.SendResponse(msg, SUCCESS)
877
878 def HandleFileUploadRequest(self, msg):
879 params = msg['params']
880
881 # Resolve upload filepath
882 filename = params['filename']
883 dest_path = filename
884
885 # If dest is specified, use it first
886 dest_path = params.get('dest', '')
887 if dest_path:
888 if not os.path.isabs(dest_path):
889 dest_path = os.path.join(os.getenv('HOME', '/tmp'), dest_path)
890
891 if os.path.isdir(dest_path):
892 dest_path = os.path.join(dest_path, filename)
893 else:
894 target_dir = os.getenv('HOME', '/tmp')
895
896 # Terminal session ID found, upload to it's current working directory
Peter Shihe6afab32018-09-11 17:16:48 +0800897 if 'terminal_sid' in params:
Wei-Ning Huangae923642015-09-24 14:08:09 +0800898 pid = self._terminal_sid_to_pid.get(params['terminal_sid'], None)
899 if pid:
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800900 try:
901 target_dir = self.GetProcessWorkingDirectory(pid)
902 except Exception as e:
903 logging.error(e)
Wei-Ning Huangae923642015-09-24 14:08:09 +0800904
905 dest_path = os.path.join(target_dir, filename)
906
907 try:
908 os.makedirs(os.path.dirname(dest_path))
909 except Exception:
910 pass
911
912 try:
913 with open(dest_path, 'w') as _:
914 pass
915 except Exception as e:
Peter Shiha78867d2018-02-26 14:17:51 +0800916 self.SendResponse(msg, str(e))
917 return
Wei-Ning Huangae923642015-09-24 14:08:09 +0800918
Wei-Ning Huangd6f69762015-10-01 21:02:07 +0800919 # If not check_only, spawn FILE mode ghost agent to handle upload
920 if not params.get('check_only', False):
921 self.SpawnGhost(self.FILE, params['sid'],
922 file_op=('upload', dest_path, params.get('perm', None)))
Wei-Ning Huang7ec55342015-09-17 08:46:06 +0800923 self.SendResponse(msg, SUCCESS)
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800924
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800925 def HandleRequest(self, msg):
Wei-Ning Huange2981862015-08-03 15:03:08 +0800926 command = msg['name']
927 params = msg['params']
928
929 if command == 'upgrade':
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800930 self.Upgrade()
Wei-Ning Huange2981862015-08-03 15:03:08 +0800931 elif command == 'terminal':
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800932 self.SpawnGhost(self.TERMINAL, params['sid'],
933 tty_device=params['tty_device'])
Wei-Ning Huang7ec55342015-09-17 08:46:06 +0800934 self.SendResponse(msg, SUCCESS)
Wei-Ning Huange2981862015-08-03 15:03:08 +0800935 elif command == 'shell':
936 self.SpawnGhost(self.SHELL, params['sid'], command=params['command'])
Wei-Ning Huang7ec55342015-09-17 08:46:06 +0800937 self.SendResponse(msg, SUCCESS)
Wei-Ning Huange2981862015-08-03 15:03:08 +0800938 elif command == 'file_download':
Wei-Ning Huangae923642015-09-24 14:08:09 +0800939 self.HandleFileDownloadRequest(msg)
Wei-Ning Huange2981862015-08-03 15:03:08 +0800940 elif command == 'clear_to_download':
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800941 self.StartDownloadServer()
Wei-Ning Huange2981862015-08-03 15:03:08 +0800942 elif command == 'file_upload':
Wei-Ning Huangae923642015-09-24 14:08:09 +0800943 self.HandleFileUploadRequest(msg)
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800944 elif command == 'forward':
945 self.SpawnGhost(self.FORWARD, params['sid'], port=params['port'])
946 self.SendResponse(msg, SUCCESS)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800947
948 def HandleResponse(self, response):
949 rid = str(response['rid'])
950 if rid in self._requests:
951 handler = self._requests[rid][2]
952 del self._requests[rid]
953 if callable(handler):
954 handler(response)
955 else:
Joel Kitching22b89042015-08-06 18:23:29 +0800956 logging.warning('Received unsolicited response, ignored')
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800957
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800958 def ParseMessage(self, buf, single=True):
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800959 if single:
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800960 try:
961 index = buf.index(_SEPARATOR)
962 except ValueError:
963 self._sock.UnRecv(buf)
964 return
965
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800966 msgs_json = [buf[:index]]
967 self._sock.UnRecv(buf[index + 2:])
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800968 else:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800969 msgs_json = buf.split(_SEPARATOR)
970 self._sock.UnRecv(msgs_json.pop())
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800971
972 for msg_json in msgs_json:
973 try:
974 msg = json.loads(msg_json)
975 except ValueError:
976 # Ignore mal-formed message.
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800977 logging.error('mal-formed JSON request, ignored')
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800978 continue
979
980 if 'name' in msg:
981 self.HandleRequest(msg)
982 elif 'response' in msg:
983 self.HandleResponse(msg)
984 else: # Ingnore mal-formed message.
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800985 logging.error('mal-formed JSON request, ignored')
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800986
987 def ScanForTimeoutRequests(self):
Joel Kitching22b89042015-08-06 18:23:29 +0800988 """Scans for pending requests which have timed out.
989
990 If any timed-out requests are discovered, their handler is called with the
991 special response value of None.
992 """
Yilin Yang78fa12e2019-09-25 14:21:10 +0800993 for rid in list(self._requests):
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800994 request_time, timeout, handler = self._requests[rid]
995 if self.Timestamp() - request_time > timeout:
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800996 if callable(handler):
997 handler(None)
998 else:
999 logging.error('Request %s timeout', rid)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001000 del self._requests[rid]
1001
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001002 def InitiateDownload(self):
1003 ttyname, filename = self._download_queue.get()
Wei-Ning Huangd521f282015-08-07 05:28:04 +08001004 sid = self._ttyname_to_sid[ttyname]
1005 self.SpawnGhost(self.FILE, terminal_sid=sid,
Wei-Ning Huangae923642015-09-24 14:08:09 +08001006 file_op=('download', filename))
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001007
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001008 def Listen(self):
1009 try:
1010 while True:
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +08001011 rds, unused_wd, unused_xd = select.select([self._sock], [], [],
Yilin Yang14d02a22019-11-01 11:32:03 +08001012 _PING_INTERVAL // 2)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001013
1014 if self._sock in rds:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +08001015 data = self._sock.Recv(_BUFSIZE)
Wei-Ning Huang09c19612015-11-24 16:29:09 +08001016
1017 # Socket is closed
Peter Shihaacbc2f2017-06-16 14:39:29 +08001018 if not data:
Wei-Ning Huang09c19612015-11-24 16:29:09 +08001019 break
1020
Wei-Ning Huanga28cd232016-01-27 15:04:41 +08001021 self.ParseMessage(data, self._register_status != SUCCESS)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001022
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001023 if (self._mode == self.AGENT and
1024 self.Timestamp() - self._last_ping > _PING_INTERVAL):
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001025 self.Ping()
1026 self.ScanForTimeoutRequests()
1027
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001028 if not self._download_queue.empty():
1029 self.InitiateDownload()
1030
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001031 if self._reset.is_set():
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001032 break
1033 except socket.error:
1034 raise RuntimeError('Connection dropped')
1035 except PingTimeoutError:
1036 raise RuntimeError('Connection timeout')
1037 finally:
Wei-Ning Huangf5311a02016-02-04 15:23:46 +08001038 self.Reset()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001039
1040 self._queue.put('resume')
1041
1042 if self._mode != Ghost.AGENT:
1043 sys.exit(1)
1044
1045 def Register(self):
1046 non_local = {}
1047 for addr in self._overlord_addrs:
1048 non_local['addr'] = addr
1049 def registered(response):
1050 if response is None:
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001051 self._reset.set()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001052 raise RuntimeError('Register request timeout')
Wei-Ning Huang63c16092015-09-18 16:20:27 +08001053
Wei-Ning Huang7ec55342015-09-17 08:46:06 +08001054 self._register_status = response['response']
1055 if response['response'] != SUCCESS:
1056 self._reset.set()
Peter Shih220a96d2016-12-22 17:02:16 +08001057 raise RuntimeError('Register: ' + response['response'])
Fei Shao0e4e2c62020-06-23 18:22:26 +08001058
1059 logging.info('Registered with Overlord at %s:%d', *non_local['addr'])
1060 self._connected_addr = non_local['addr']
1061 self.Upgrade() # Check for upgrade
1062 self._queue.put('pause', True)
Wei-Ning Huang63c16092015-09-18 16:20:27 +08001063
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001064 try:
1065 logging.info('Trying %s:%d ...', *addr)
1066 self.Reset()
Wei-Ning Huangf5311a02016-02-04 15:23:46 +08001067
Peter Shih220a96d2016-12-22 17:02:16 +08001068 # Check if server has TLS enabled. Only check if self._tls_mode is
1069 # None.
Wei-Ning Huangb6605d22016-06-22 17:33:37 +08001070 # Only control channel needs to determine if TLS is enabled. Other mode
1071 # should use the TLSSettings passed in when it was spawned.
1072 if self._mode == Ghost.AGENT:
Peter Shih220a96d2016-12-22 17:02:16 +08001073 self._tls_settings.SetEnabled(
1074 self.TLSEnabled(*addr) if self._tls_mode is None
1075 else self._tls_mode)
Wei-Ning Huang47c79b82016-05-24 01:24:46 +08001076
Wei-Ning Huangf5311a02016-02-04 15:23:46 +08001077 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1078 sock.settimeout(_CONNECT_TIMEOUT)
1079
1080 try:
1081 if self._tls_settings.Enabled():
1082 tls_context = self._tls_settings.Context()
1083 sock = tls_context.wrap_socket(sock, server_hostname=addr[0])
1084
1085 sock.connect(addr)
1086 except (ssl.SSLError, ssl.CertificateError) as e:
1087 logging.error('%s: %s', e.__class__.__name__, e)
1088 continue
1089 except IOError as e:
1090 if e.errno == 2: # No such file or directory
1091 logging.error('%s: %s', e.__class__.__name__, e)
1092 continue
1093 raise
1094
1095 self._sock = BufferedSocket(sock)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001096
1097 logging.info('Connection established, registering...')
1098 handler = {
1099 Ghost.AGENT: registered,
Wei-Ning Huangb8461202015-09-01 20:07:41 +08001100 Ghost.TERMINAL: self.SpawnTTYServer,
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001101 Ghost.SHELL: self.SpawnShellServer,
1102 Ghost.FILE: self.InitiateFileOperation,
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +08001103 Ghost.FORWARD: self.SpawnPortForwardServer,
Peter Shihe6afab32018-09-11 17:16:48 +08001104 }[self._mode]
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001105
1106 # Machine ID may change if MAC address is used (USB-ethernet dongle
1107 # plugged/unplugged)
1108 self._machine_id = self.GetMachineID()
Wei-Ning Huang7d029b12015-03-06 10:32:15 +08001109 self.SendRequest('register',
1110 {'mode': self._mode, 'mid': self._machine_id,
Wei-Ning Huangfed95862015-08-07 03:17:11 +08001111 'sid': self._session_id,
Wei-Ning Huang7d029b12015-03-06 10:32:15 +08001112 'properties': self._properties}, handler)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001113 except socket.error:
1114 pass
1115 else:
Yilin Yangcb5e8922020-12-16 10:30:44 +08001116 # We only send dut data when it's agent mode.
1117 if self._mode == Ghost.AGENT:
1118 self.SendData()
Yilin Yang89a136c2021-01-05 15:25:05 +08001119 if self._track_connection is not None:
1120 self.TrackConnection(self._track_connection,
1121 self._track_connection_timeout_secs)
Wei-Ning Huangf5311a02016-02-04 15:23:46 +08001122 sock.settimeout(None)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001123 self.Listen()
1124
Moja Hsuc9ecc8b2015-07-13 11:39:17 +08001125 raise RuntimeError('Cannot connect to any server')
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001126
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001127 def Reconnect(self):
1128 logging.info('Received reconnect request from RPC server, reconnecting...')
1129 self._reset.set()
1130
Yilin Yang64dd8592020-11-10 16:16:23 +08001131 def CollectPytestAndStatus(self):
1132 STATUS = Enum(['failed', 'running', 'idle'])
1133 goofy = state.GetInstance()
1134 tests = goofy.GetTests()
1135
1136 # Ignore parents
1137 tests = [x for x in tests if not x.get('parent')]
1138
1139 scheduled_tests = (
1140 goofy.GetTestRunStatus(None).get('scheduled_tests') or [])
1141 scheduled_tests = {t['path']
1142 for t in scheduled_tests}
1143 tests = [x for x in tests if x['path'] in scheduled_tests]
1144 data = {
1145 'pytest': '',
1146 'status': STATUS.idle
1147 }
1148
1149 def parse_pytest_name(test):
1150 # test['path'] format: 'test_list_name:pytest_name'
1151 return test['path'][test['path'].index(':') + 1:]
1152
1153 for test in filter(lambda t: t['status'] == TestState.ACTIVE, tests):
1154 data['pytest'] = parse_pytest_name(test)
1155 data['status'] = STATUS.running
1156 return data
1157
1158 for test in filter(lambda t: t['status'] == TestState.FAILED, tests):
1159 data['pytest'] = parse_pytest_name(test)
1160 data['status'] = STATUS.failed
1161 return data
1162
1163 if tests:
1164 data['pytest'] = parse_pytest_name(tests[0])
1165
1166 return data
1167
1168 def CollectModelName(self):
1169 return {
1170 'model': cros_config_module.CrosConfig().GetModelName()
1171 }
1172
1173 def CollectIP(self):
1174 ip_addrs = []
1175 for interface in net_utils.GetNetworkInterfaces():
1176 ip = net_utils.GetEthernetIp(interface)[0]
1177 if ip:
1178 ip_addrs.append(ip)
1179
1180 return {
1181 'ip': ip_addrs
1182 }
1183
Yilin Yangdc347592021-02-03 11:33:04 +08001184 def CollectSerial(self):
1185 return {
1186 'serial': device_data.GetSerialNumber()
1187 }
1188
Yilin Yang64dd8592020-11-10 16:16:23 +08001189 def CollectData(self):
1190 """Collect dut data.
1191
1192 Data includes:
1193 1. status: Current test status
1194 2. pytest: Current pytest
1195 3. model: Model name
1196 4. ip: IP
Yilin Yangdc347592021-02-03 11:33:04 +08001197 5. serial: Serial number
Yilin Yang64dd8592020-11-10 16:16:23 +08001198 """
1199 data = {}
1200 data.update(self.CollectPytestAndStatus())
1201 data.update(self.CollectModelName())
1202 data.update(self.CollectIP())
Yilin Yangdc347592021-02-03 11:33:04 +08001203 data.update(self.CollectSerial())
Yilin Yang64dd8592020-11-10 16:16:23 +08001204
1205 return data
1206
1207 def SendData(self):
1208 if not sys_utils.InCrOSDevice():
1209 return
1210
1211 data = self.CollectData()
1212 logging.info('data = %s', data)
1213
1214 self.SendRequest('update_dut_data', data)
1215
Yilin Yang90c60c42020-11-11 14:45:08 +08001216 def TrackConnection(self, enabled, timeout_secs):
1217 logging.info('TrackConnection, enabled = %s, timeout_secs = %d', enabled,
1218 timeout_secs)
1219
Yilin Yang89a136c2021-01-05 15:25:05 +08001220 self._track_connection = enabled
1221 self._track_connection_timeout_secs = timeout_secs
Yilin Yang90c60c42020-11-11 14:45:08 +08001222 self.SendRequest('track_connection', {
1223 'enabled': enabled,
1224 'timeout_secs': timeout_secs
1225 })
1226
Wei-Ning Huang7ec55342015-09-17 08:46:06 +08001227 def GetStatus(self):
Peter Shih5cafebb2017-06-30 16:36:22 +08001228 status = self._register_status
1229 if self._register_status == SUCCESS:
1230 ip, port = self._sock.sock.getpeername()
1231 status += ' %s:%d' % (ip, port)
1232 return status
Wei-Ning Huang7ec55342015-09-17 08:46:06 +08001233
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001234 def AddToDownloadQueue(self, ttyname, filename):
1235 self._download_queue.put((ttyname, filename))
1236
Wei-Ning Huangd521f282015-08-07 05:28:04 +08001237 def RegisterTTY(self, session_id, ttyname):
1238 self._ttyname_to_sid[ttyname] = session_id
Wei-Ning Huange2981862015-08-03 15:03:08 +08001239
1240 def RegisterSession(self, session_id, process_id):
1241 self._terminal_sid_to_pid[session_id] = process_id
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001242
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001243 def StartLanDiscovery(self):
1244 """Start to listen to LAN discovery packet at
1245 _OVERLORD_LAN_DISCOVERY_PORT."""
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001246
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001247 def thread_func():
1248 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
1249 s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
1250 s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001251 try:
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001252 s.bind(('0.0.0.0', _OVERLORD_LAN_DISCOVERY_PORT))
1253 except socket.error as e:
Moja Hsuc9ecc8b2015-07-13 11:39:17 +08001254 logging.error('LAN discovery: %s, abort', e)
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001255 return
1256
1257 logging.info('LAN Discovery: started')
1258 while True:
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +08001259 rd, unused_wd, unused_xd = select.select([s], [], [], 1)
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001260
1261 if s in rd:
1262 data, source_addr = s.recvfrom(_BUFSIZE)
1263 parts = data.split()
1264 if parts[0] == 'OVERLORD':
1265 ip, port = parts[1].split(':')
1266 if not ip:
1267 ip = source_addr[0]
1268 self._queue.put((ip, int(port)), True)
1269
1270 try:
1271 obj = self._queue.get(False)
Yilin Yang8b7f5192020-01-08 11:43:00 +08001272 except queue.Empty:
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001273 pass
1274 else:
Peter Shihaacbc2f2017-06-16 14:39:29 +08001275 if not isinstance(obj, str):
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001276 self._queue.put(obj)
1277 elif obj == 'pause':
1278 logging.info('LAN Discovery: paused')
1279 while obj != 'resume':
1280 obj = self._queue.get(True)
1281 logging.info('LAN Discovery: resumed')
1282
1283 t = threading.Thread(target=thread_func)
1284 t.daemon = True
1285 t.start()
1286
1287 def StartRPCServer(self):
Joel Kitching22b89042015-08-06 18:23:29 +08001288 logging.info('RPC Server: started')
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001289 rpc_server = SimpleJSONRPCServer((_DEFAULT_BIND_ADDRESS, _GHOST_RPC_PORT),
1290 logRequests=False)
Yilin Yang64dd8592020-11-10 16:16:23 +08001291 rpc_server.register_function(self.SendData, 'SendData')
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001292 rpc_server.register_function(self.Reconnect, 'Reconnect')
Wei-Ning Huang7ec55342015-09-17 08:46:06 +08001293 rpc_server.register_function(self.GetStatus, 'GetStatus')
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001294 rpc_server.register_function(self.RegisterTTY, 'RegisterTTY')
Wei-Ning Huange2981862015-08-03 15:03:08 +08001295 rpc_server.register_function(self.RegisterSession, 'RegisterSession')
Yilin Yang90c60c42020-11-11 14:45:08 +08001296 rpc_server.register_function(self.TrackConnection, 'TrackConnection')
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001297 rpc_server.register_function(self.AddToDownloadQueue, 'AddToDownloadQueue')
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001298 t = threading.Thread(target=rpc_server.serve_forever)
1299 t.daemon = True
1300 t.start()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001301
Yilin Yang1512d972020-11-19 13:34:42 +08001302 def ApplyTestListParams(self):
1303 mgr = manager.Manager()
Cheng Yueh59a29662020-12-02 12:20:18 +08001304 device = sys_interface.SystemInterface()
1305 constants = mgr.GetTestListByID(mgr.GetActiveTestListId(device)).constants
Yilin Yang1512d972020-11-19 13:34:42 +08001306
1307 if 'overlord' not in constants:
1308 return
1309
1310 if 'overlord_urls' in constants['overlord']:
1311 for addr in [(x, _OVERLORD_PORT) for x in
1312 constants['overlord']['overlord_urls']]:
1313 if addr not in self._overlord_addrs:
1314 self._overlord_addrs.append(addr)
1315
1316 # This is sugar for ODM to turn off the verification quickly if they forgot.
1317 # So we don't support to turn on again.
1318 # If we want to turn on, we need to restart the ghost daemon.
1319 if 'tls_no_verify' in constants['overlord']:
1320 if constants['overlord']['tls_no_verify']:
1321 self._tls_settings = TLSSettings(None, False)
1322
Wei-Ning Huang829e0c82015-05-26 14:37:23 +08001323 def ScanServer(self):
Hung-Te Lin41ff8f32017-08-30 08:10:39 +08001324 for meth in [self.GetGateWayIP, self.GetFactoryServerIP]:
Wei-Ning Huang829e0c82015-05-26 14:37:23 +08001325 for addr in [(x, _OVERLORD_PORT) for x in meth()]:
1326 if addr not in self._overlord_addrs:
1327 self._overlord_addrs.append(addr)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001328
Wei-Ning Huang11c35022015-10-21 16:52:32 +08001329 def Start(self, lan_disc=False, rpc_server=False):
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001330 logging.info('%s started', self.MODE_NAME[self._mode])
1331 logging.info('MID: %s', self._machine_id)
Wei-Ning Huangfed95862015-08-07 03:17:11 +08001332 logging.info('SID: %s', self._session_id)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001333
Wei-Ning Huangb05cde32015-08-01 09:48:41 +08001334 # We don't care about child process's return code, not wait is needed. This
1335 # is used to prevent zombie process from lingering in the system.
1336 self.SetIgnoreChild(True)
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001337
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001338 if lan_disc:
1339 self.StartLanDiscovery()
1340
1341 if rpc_server:
1342 self.StartRPCServer()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001343
1344 try:
1345 while True:
1346 try:
1347 addr = self._queue.get(False)
Yilin Yang8b7f5192020-01-08 11:43:00 +08001348 except queue.Empty:
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001349 pass
1350 else:
Peter Shihaacbc2f2017-06-16 14:39:29 +08001351 if isinstance(addr, tuple) and addr not in self._overlord_addrs:
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001352 logging.info('LAN Discovery: got overlord address %s:%d', *addr)
1353 self._overlord_addrs.append(addr)
1354
Yilin Yang1512d972020-11-19 13:34:42 +08001355 if self._mode == Ghost.AGENT:
1356 self.ApplyTestListParams()
1357
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001358 try:
Wei-Ning Huang829e0c82015-05-26 14:37:23 +08001359 self.ScanServer()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001360 self.Register()
Joel Kitching22b89042015-08-06 18:23:29 +08001361 # Don't show stack trace for RuntimeError, which we use in this file for
1362 # plausible and expected errors (such as can't connect to server).
1363 except RuntimeError as e:
Yilin Yang58948af2019-10-30 18:28:55 +08001364 logging.info('%s, retrying in %ds', str(e), _RETRY_INTERVAL)
Joel Kitching22b89042015-08-06 18:23:29 +08001365 time.sleep(_RETRY_INTERVAL)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001366 except Exception as e:
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +08001367 unused_x, unused_y, exc_traceback = sys.exc_info()
Joel Kitching22b89042015-08-06 18:23:29 +08001368 traceback.print_tb(exc_traceback)
1369 logging.info('%s: %s, retrying in %ds',
Yilin Yang58948af2019-10-30 18:28:55 +08001370 e.__class__.__name__, str(e), _RETRY_INTERVAL)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001371 time.sleep(_RETRY_INTERVAL)
1372
1373 self.Reset()
1374 except KeyboardInterrupt:
1375 logging.error('Received keyboard interrupt, quit')
1376 sys.exit(0)
1377
1378
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001379def GhostRPCServer():
Wei-Ning Huang8037c182015-09-19 04:41:50 +08001380 """Returns handler to Ghost's JSON RPC server."""
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001381 return jsonrpclib.Server('http://localhost:%d' % _GHOST_RPC_PORT)
1382
1383
Wei-Ning Huang8037c182015-09-19 04:41:50 +08001384def ForkToBackground():
1385 """Fork process to run in background."""
1386 pid = os.fork()
1387 if pid != 0:
1388 logging.info('Ghost(%d) running in background.', pid)
1389 sys.exit(0)
1390
1391
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001392def DownloadFile(filename):
Wei-Ning Huang8037c182015-09-19 04:41:50 +08001393 """Initiate a client-initiated file download."""
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001394 filepath = os.path.abspath(filename)
1395 if not os.path.exists(filepath):
Joel Kitching22b89042015-08-06 18:23:29 +08001396 logging.error('file `%s\' does not exist', filename)
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001397 sys.exit(1)
1398
1399 # Check if we actually have permission to read the file
1400 if not os.access(filepath, os.R_OK):
Joel Kitching22b89042015-08-06 18:23:29 +08001401 logging.error('can not open %s for reading', filepath)
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001402 sys.exit(1)
1403
1404 server = GhostRPCServer()
1405 server.AddToDownloadQueue(os.ttyname(0), filepath)
1406 sys.exit(0)
1407
1408
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001409def main():
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +08001410 # Setup logging format
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001411 logger = logging.getLogger()
1412 logger.setLevel(logging.INFO)
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +08001413 handler = logging.StreamHandler()
1414 formatter = logging.Formatter('%(asctime)s %(message)s', '%Y/%m/%d %H:%M:%S')
1415 handler.setFormatter(formatter)
1416 logger.addHandler(handler)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001417
Wei-Ning Huang7d029b12015-03-06 10:32:15 +08001418 parser = argparse.ArgumentParser()
Wei-Ning Huang8037c182015-09-19 04:41:50 +08001419 parser.add_argument('--fork', dest='fork', action='store_true', default=False,
1420 help='fork procecess to run in background')
Wei-Ning Huangc9c97f02015-05-19 15:05:42 +08001421 parser.add_argument('--mid', metavar='MID', dest='mid', action='store',
1422 default=None, help='use MID as machine ID')
1423 parser.add_argument('--rand-mid', dest='mid', action='store_const',
1424 const=Ghost.RANDOM_MID, help='use random machine ID')
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001425 parser.add_argument('--no-lan-disc', dest='lan_disc', action='store_false',
1426 default=True, help='disable LAN discovery')
1427 parser.add_argument('--no-rpc-server', dest='rpc_server',
1428 action='store_false', default=True,
1429 help='disable RPC server')
Peter Shih220a96d2016-12-22 17:02:16 +08001430 parser.add_argument('--tls', dest='tls_mode', default='detect',
1431 choices=('y', 'n', 'detect'),
1432 help="specify 'y' or 'n' to force enable/disable TLS")
Yilin Yang64dd8592020-11-10 16:16:23 +08001433 parser.add_argument(
1434 '--tls-cert-file', metavar='TLS_CERT_FILE', dest='tls_cert_file',
1435 type=str, default=None,
1436 help='file containing the server TLS certificate in PEM '
1437 'format')
Wei-Ning Huang47c79b82016-05-24 01:24:46 +08001438 parser.add_argument('--tls-no-verify', dest='tls_no_verify',
1439 action='store_true', default=False,
1440 help='do not verify certificate if TLS is enabled')
Yilin Yang64dd8592020-11-10 16:16:23 +08001441 parser.add_argument(
1442 '--prop-file', metavar='PROP_FILE', dest='prop_file', type=str,
1443 default=None, help='file containing the JSON representation of client '
1444 'properties')
Yilin Yang77668412021-02-02 10:53:36 +08001445 parser.add_argument('--ovl-path', metavar='OVL_PATH', dest='ovl_path',
1446 type=str, default=None, help='path to ovl tool')
1447 parser.add_argument('--certificate-dir', metavar='CERTIFICATE_DIR',
1448 dest='certificate_dir', type=str, default=None,
1449 help='path to overlord certificate directory')
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001450 parser.add_argument('--download', metavar='FILE', dest='download', type=str,
1451 default=None, help='file to download')
Wei-Ning Huang23ed0162015-09-18 14:42:03 +08001452 parser.add_argument('--reset', dest='reset', default=False,
1453 action='store_true',
1454 help='reset ghost and reload all configs')
Yilin Yang64dd8592020-11-10 16:16:23 +08001455 parser.add_argument('--send-data', dest='send_data', default=False,
1456 action='store_true',
1457 help='send client data to overlord server')
Peter Shih5cafebb2017-06-30 16:36:22 +08001458 parser.add_argument('--status', dest='status', default=False,
1459 action='store_true',
1460 help='show status of the client')
Yilin Yang90c60c42020-11-11 14:45:08 +08001461 parser.add_argument('--track-connection', dest='track_connection',
1462 default=None, choices=('y', 'n'),
1463 help="specify 'y' or 'n' to track connection or not")
1464 parser.add_argument('--timeout-seconds', dest='timeout_secs', type=int,
1465 default=900,
1466 help='timeout seconds when track the connection')
Yilin Yang64dd8592020-11-10 16:16:23 +08001467 parser.add_argument('overlord_ip', metavar='OVERLORD_IP', type=str, nargs='*',
1468 help='overlord server address')
Wei-Ning Huang7d029b12015-03-06 10:32:15 +08001469 args = parser.parse_args()
1470
Peter Shih5cafebb2017-06-30 16:36:22 +08001471 if args.status:
1472 print(GhostRPCServer().GetStatus())
1473 sys.exit()
1474
Wei-Ning Huang8037c182015-09-19 04:41:50 +08001475 if args.fork:
1476 ForkToBackground()
1477
Wei-Ning Huang23ed0162015-09-18 14:42:03 +08001478 if args.reset:
1479 GhostRPCServer().Reconnect()
1480 sys.exit()
1481
Yilin Yang64dd8592020-11-10 16:16:23 +08001482 if args.send_data:
1483 GhostRPCServer().SendData()
1484 sys.exit()
1485
Yilin Yang90c60c42020-11-11 14:45:08 +08001486 if args.track_connection:
1487 GhostRPCServer().TrackConnection(args.track_connection == 'y',
1488 args.timeout_secs)
1489 sys.exit()
1490
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001491 if args.download:
1492 DownloadFile(args.download)
1493
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001494 addrs = [('localhost', _OVERLORD_PORT)]
Wei-Ning Huang47c79b82016-05-24 01:24:46 +08001495 addrs = [(x, _OVERLORD_PORT) for x in args.overlord_ip] + addrs
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001496
Wei-Ning Huangf5311a02016-02-04 15:23:46 +08001497 prop_file = os.path.abspath(args.prop_file) if args.prop_file else None
1498
Wei-Ning Huang47c79b82016-05-24 01:24:46 +08001499 tls_settings = TLSSettings(args.tls_cert_file, not args.tls_no_verify)
Peter Shih220a96d2016-12-22 17:02:16 +08001500 tls_mode = args.tls_mode
1501 tls_mode = {'y': True, 'n': False, 'detect': None}[tls_mode]
Yilin Yang77668412021-02-02 10:53:36 +08001502 g = Ghost(addrs, tls_settings, Ghost.AGENT, args.mid, prop_file=prop_file,
1503 tls_mode=tls_mode, ovl_path=args.ovl_path,
1504 certificate_dir=args.certificate_dir)
Wei-Ning Huang11c35022015-10-21 16:52:32 +08001505 g.Start(args.lan_disc, args.rpc_server)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001506
1507
1508if __name__ == '__main__':
Wei-Ning Huangf5311a02016-02-04 15:23:46 +08001509 try:
1510 main()
1511 except Exception as e:
1512 logging.error(e)