blob: bebf6104312e7ba3a70e1979d4b76fbe6319517d [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 Yang1512d972020-11-19 13:34:42 +080038from cros.factory.test.test_lists import manager
Yilin Yang42ba5c62020-05-05 10:32:34 +080039from cros.factory.utils import process_utils
40
Yilin Yangf54fb912020-01-08 11:42:38 +080041
Fei Shaobb0a3e62020-06-20 15:41:25 +080042_GHOST_RPC_PORT = int(os.getenv('GHOST_RPC_PORT', '4499'))
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080043
Fei Shaobb0a3e62020-06-20 15:41:25 +080044_OVERLORD_PORT = int(os.getenv('OVERLORD_PORT', '4455'))
45_OVERLORD_LAN_DISCOVERY_PORT = int(os.getenv('OVERLORD_LD_PORT', '4456'))
46_OVERLORD_HTTP_PORT = int(os.getenv('OVERLORD_HTTP_PORT', '9000'))
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080047
48_BUFSIZE = 8192
49_RETRY_INTERVAL = 2
Yilin Yang6b9ec9d2019-12-09 11:04:06 +080050_SEPARATOR = b'\r\n'
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080051_PING_TIMEOUT = 3
52_PING_INTERVAL = 5
53_REQUEST_TIMEOUT_SECS = 60
54_SHELL = os.getenv('SHELL', '/bin/bash')
Wei-Ning Huang7dbf4a72016-03-02 20:16:20 +080055_DEFAULT_BIND_ADDRESS = 'localhost'
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080056
Moja Hsuc9ecc8b2015-07-13 11:39:17 +080057_CONTROL_START = 128
58_CONTROL_END = 129
59
Wei-Ning Huanga301f572015-06-03 17:34:21 +080060_BLOCK_SIZE = 4096
Wei-Ning Huange0def6a2015-11-05 15:41:24 +080061_CONNECT_TIMEOUT = 3
Wei-Ning Huanga301f572015-06-03 17:34:21 +080062
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +080063# Stream control
64_STDIN_CLOSED = '##STDIN_CLOSED##'
65
Wei-Ning Huang7ec55342015-09-17 08:46:06 +080066SUCCESS = 'success'
67FAILED = 'failed'
68DISCONNECTED = 'disconnected'
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080069
Joel Kitching22b89042015-08-06 18:23:29 +080070
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080071class PingTimeoutError(Exception):
72 pass
73
74
75class RequestError(Exception):
76 pass
77
78
Fei Shaobd07c9a2020-06-15 19:04:50 +080079class BufferedSocket:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +080080 """A buffered socket that supports unrecv.
81
82 Allow putting back data back to the socket for the next recv() call.
83 """
Wei-Ning Huangf5311a02016-02-04 15:23:46 +080084 def __init__(self, sock):
85 self.sock = sock
Yilin Yang6b9ec9d2019-12-09 11:04:06 +080086 self._buf = b''
Wei-Ning Huanga28cd232016-01-27 15:04:41 +080087
Wei-Ning Huangf5311a02016-02-04 15:23:46 +080088 def fileno(self):
89 return self.sock.fileno()
90
Wei-Ning Huanga28cd232016-01-27 15:04:41 +080091 def Recv(self, bufsize, flags=0):
92 if self._buf:
93 if len(self._buf) >= bufsize:
94 ret = self._buf[:bufsize]
95 self._buf = self._buf[bufsize:]
96 return ret
Yilin Yang15a3f8f2020-01-03 17:49:00 +080097 ret = self._buf
Yilin Yang6b9ec9d2019-12-09 11:04:06 +080098 self._buf = b''
Yilin Yang15a3f8f2020-01-03 17:49:00 +080099 return ret + self.sock.recv(bufsize - len(ret), flags)
100 return self.sock.recv(bufsize, flags)
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800101
102 def UnRecv(self, buf):
103 self._buf = buf + self._buf
104
105 def Send(self, *args, **kwargs):
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800106 return self.sock.send(*args, **kwargs)
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800107
108 def RecvBuf(self):
109 """Only recive from buffer."""
110 ret = self._buf
Yilin Yang6b9ec9d2019-12-09 11:04:06 +0800111 self._buf = b''
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800112 return ret
113
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800114 def Close(self):
115 self.sock.close()
116
117
Fei Shaobd07c9a2020-06-15 19:04:50 +0800118class TLSSettings:
Wei-Ning Huang47c79b82016-05-24 01:24:46 +0800119 def __init__(self, tls_cert_file, verify):
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800120 """Constructor.
121
122 Args:
123 tls_cert_file: TLS certificate in PEM format.
124 enable_tls_without_verify: enable TLS but don't verify certificate.
125 """
Wei-Ning Huang47c79b82016-05-24 01:24:46 +0800126 self._enabled = False
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800127 self._tls_cert_file = tls_cert_file
Wei-Ning Huang47c79b82016-05-24 01:24:46 +0800128 self._verify = verify
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800129 self._tls_context = None
130
Wei-Ning Huang47c79b82016-05-24 01:24:46 +0800131 def _UpdateContext(self):
132 if not self._enabled:
133 self._tls_context = None
134 return
135
136 self._tls_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
137 self._tls_context.verify_mode = ssl.CERT_REQUIRED
138
139 if self._verify:
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800140 if self._tls_cert_file:
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800141 self._tls_context.check_hostname = True
142 try:
143 self._tls_context.load_verify_locations(self._tls_cert_file)
Wei-Ning Huang47c79b82016-05-24 01:24:46 +0800144 logging.info('TLSSettings: using user-supplied ca-certificate')
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800145 except IOError as e:
146 logging.error('TLSSettings: %s: %s', self._tls_cert_file, e)
147 sys.exit(1)
Wei-Ning Huang47c79b82016-05-24 01:24:46 +0800148 else:
149 self._tls_context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
150 logging.info('TLSSettings: using built-in ca-certificates')
151 else:
152 self._tls_context.verify_mode = ssl.CERT_NONE
153 logging.info('TLSSettings: skipping TLS verification!!!')
154
155 def SetEnabled(self, enabled):
156 logging.info('TLSSettings: enabled: %s', enabled)
157
158 if self._enabled != enabled:
159 self._enabled = enabled
160 self._UpdateContext()
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800161
162 def Enabled(self):
Wei-Ning Huang47c79b82016-05-24 01:24:46 +0800163 return self._enabled
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800164
165 def Context(self):
166 return self._tls_context
167
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800168
Fei Shaobd07c9a2020-06-15 19:04:50 +0800169class Ghost:
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800170 """Ghost implements the client protocol of Overlord.
171
172 Ghost provide terminal/shell/logcat functionality and manages the client
173 side connectivity.
174 """
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800175 NONE, AGENT, TERMINAL, SHELL, LOGCAT, FILE, FORWARD = range(7)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800176
177 MODE_NAME = {
178 NONE: 'NONE',
179 AGENT: 'Agent',
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +0800180 TERMINAL: 'Terminal',
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800181 SHELL: 'Shell',
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800182 LOGCAT: 'Logcat',
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800183 FILE: 'File',
184 FORWARD: 'Forward'
Peter Shihe6afab32018-09-11 17:16:48 +0800185 }
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800186
Wei-Ning Huangc9c97f02015-05-19 15:05:42 +0800187 RANDOM_MID = '##random_mid##'
188
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800189 def __init__(self, overlord_addrs, tls_settings=None, mode=AGENT, mid=None,
190 sid=None, prop_file=None, terminal_sid=None, tty_device=None,
Peter Shih220a96d2016-12-22 17:02:16 +0800191 command=None, file_op=None, port=None, tls_mode=None):
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800192 """Constructor.
193
194 Args:
195 overlord_addrs: a list of possible address of overlord.
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800196 tls_settings: a TLSSetting object.
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800197 mode: client mode, either AGENT, SHELL or LOGCAT
Wei-Ning Huangc9c97f02015-05-19 15:05:42 +0800198 mid: a str to set for machine ID. If mid equals Ghost.RANDOM_MID, machine
199 id is randomly generated.
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800200 sid: session ID. If the connection is requested by overlord, sid should
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800201 be set to the corresponding session id assigned by overlord.
Wei-Ning Huang23ed0162015-09-18 14:42:03 +0800202 prop_file: properties file filename.
Wei-Ning Huangd521f282015-08-07 05:28:04 +0800203 terminal_sid: the terminal session ID associate with this client. This is
204 use for file download.
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800205 tty_device: the terminal device to open, if tty_device is None, as pseudo
206 terminal will be opened instead.
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800207 command: the command to execute when we are in SHELL mode.
Wei-Ning Huang8ee3bcd2015-10-01 17:10:01 +0800208 file_op: a tuple (action, filepath, perm). action is either 'download' or
209 'upload'. perm is the permission to set for the file.
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800210 port: port number to forward.
Peter Shih220a96d2016-12-22 17:02:16 +0800211 tls_mode: can be [True, False, None]. if not None, skip detection of
212 TLS and assume whether server use TLS or not.
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800213 """
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800214 assert mode in [Ghost.AGENT, Ghost.TERMINAL, Ghost.SHELL, Ghost.FILE,
215 Ghost.FORWARD]
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +0800216 if mode == Ghost.SHELL:
217 assert command is not None
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800218 if mode == Ghost.FILE:
219 assert file_op is not None
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800220
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800221 self._platform = platform.system()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800222 self._overlord_addrs = overlord_addrs
Wei-Ning Huangad330c52015-03-12 20:34:18 +0800223 self._connected_addr = None
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800224 self._tls_settings = tls_settings
Wei-Ning Huangc9c97f02015-05-19 15:05:42 +0800225 self._mid = mid
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800226 self._sock = None
Wei-Ning Huang7ec55342015-09-17 08:46:06 +0800227 self._mode = mode
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800228 self._machine_id = self.GetMachineID()
Wei-Ning Huangfed95862015-08-07 03:17:11 +0800229 self._session_id = sid if sid is not None else str(uuid.uuid4())
Wei-Ning Huangd521f282015-08-07 05:28:04 +0800230 self._terminal_session_id = terminal_sid
Wei-Ning Huang23ed0162015-09-18 14:42:03 +0800231 self._ttyname_to_sid = {}
232 self._terminal_sid_to_pid = {}
233 self._prop_file = prop_file
Wei-Ning Huang7d029b12015-03-06 10:32:15 +0800234 self._properties = {}
Wei-Ning Huang7ec55342015-09-17 08:46:06 +0800235 self._register_status = DISCONNECTED
236 self._reset = threading.Event()
Peter Shih220a96d2016-12-22 17:02:16 +0800237 self._tls_mode = tls_mode
Wei-Ning Huang7ec55342015-09-17 08:46:06 +0800238
239 # RPC
Wei-Ning Huang7ec55342015-09-17 08:46:06 +0800240 self._requests = {}
Yilin Yang8b7f5192020-01-08 11:43:00 +0800241 self._queue = queue.Queue()
Wei-Ning Huang7ec55342015-09-17 08:46:06 +0800242
243 # Protocol specific
244 self._last_ping = 0
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800245 self._tty_device = tty_device
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +0800246 self._shell_command = command
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800247 self._file_op = file_op
Yilin Yang8b7f5192020-01-08 11:43:00 +0800248 self._download_queue = queue.Queue()
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800249 self._port = port
Wei-Ning Huang7ec55342015-09-17 08:46:06 +0800250
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800251 def SetIgnoreChild(self, status):
252 # Only ignore child for Agent since only it could spawn child Ghost.
253 if self._mode == Ghost.AGENT:
254 signal.signal(signal.SIGCHLD,
255 signal.SIG_IGN if status else signal.SIG_DFL)
256
257 def GetFileSha1(self, filename):
Yilin Yang0412c272019-12-05 16:57:40 +0800258 with open(filename, 'rb') as f:
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800259 return hashlib.sha1(f.read()).hexdigest()
260
Wei-Ning Huang47c79b82016-05-24 01:24:46 +0800261 def TLSEnabled(self, host, port):
262 """Determine if TLS is enabled on given server address."""
Wei-Ning Huang58833882015-09-16 16:52:37 +0800263 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
264 try:
Wei-Ning Huang47c79b82016-05-24 01:24:46 +0800265 # Allow any certificate since we only want to check if server talks TLS.
266 context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
267 context.verify_mode = ssl.CERT_NONE
Wei-Ning Huang58833882015-09-16 16:52:37 +0800268
Wei-Ning Huang47c79b82016-05-24 01:24:46 +0800269 sock = context.wrap_socket(sock, server_hostname=host)
270 sock.settimeout(_CONNECT_TIMEOUT)
271 sock.connect((host, port))
272 return True
Wei-Ning Huangb6605d22016-06-22 17:33:37 +0800273 except ssl.SSLError:
274 return False
Stimim Chen3899a912020-07-17 10:52:03 +0800275 except socket.timeout:
276 return False
Wei-Ning Huang47c79b82016-05-24 01:24:46 +0800277 except socket.error: # Connect refused or timeout
278 raise
Wei-Ning Huang58833882015-09-16 16:52:37 +0800279 except Exception:
Wei-Ning Huang47c79b82016-05-24 01:24:46 +0800280 return False # For whatever reason above failed, assume False
Wei-Ning Huang58833882015-09-16 16:52:37 +0800281
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800282 def Upgrade(self):
283 logging.info('Upgrade: initiating upgrade sequence...')
284
Wei-Ning Huang47c79b82016-05-24 01:24:46 +0800285 try:
Wei-Ning Huangb6605d22016-06-22 17:33:37 +0800286 https_enabled = self.TLSEnabled(self._connected_addr[0],
287 _OVERLORD_HTTP_PORT)
Wei-Ning Huang47c79b82016-05-24 01:24:46 +0800288 except socket.error:
Wei-Ning Huangb6605d22016-06-22 17:33:37 +0800289 logging.error('Upgrade: failed to connect to Overlord HTTP server, '
290 'abort')
291 return
Wei-Ning Huang47c79b82016-05-24 01:24:46 +0800292
293 if self._tls_settings.Enabled() and not https_enabled:
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800294 logging.error('Upgrade: TLS enforced but found Overlord HTTP server '
295 'without TLS enabled! Possible mis-configuration or '
296 'DNS/IP spoofing detected, abort')
297 return
298
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800299 scriptpath = os.path.abspath(sys.argv[0])
Wei-Ning Huang03f9f762015-09-16 21:51:35 +0800300 url = 'http%s://%s:%d/upgrade/ghost.py' % (
Wei-Ning Huang47c79b82016-05-24 01:24:46 +0800301 's' if https_enabled else '', self._connected_addr[0],
Wei-Ning Huang03f9f762015-09-16 21:51:35 +0800302 _OVERLORD_HTTP_PORT)
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800303
304 # Download sha1sum for ghost.py for verification
305 try:
Wei-Ning Huange0def6a2015-11-05 15:41:24 +0800306 with contextlib.closing(
Yilin Yangf54fb912020-01-08 11:42:38 +0800307 urllib.request.urlopen(url + '.sha1', timeout=_CONNECT_TIMEOUT,
308 context=self._tls_settings.Context())) as f:
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800309 if f.getcode() != 200:
310 raise RuntimeError('HTTP status %d' % f.getcode())
311 sha1sum = f.read().strip()
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800312 except (ssl.SSLError, ssl.CertificateError) as e:
313 logging.error('Upgrade: %s: %s', e.__class__.__name__, e)
314 return
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800315 except Exception:
316 logging.error('Upgrade: failed to download sha1sum file, abort')
317 return
318
319 if self.GetFileSha1(scriptpath) == sha1sum:
320 logging.info('Upgrade: ghost is already up-to-date, skipping upgrade')
321 return
322
323 # Download upgrade version of ghost.py
324 try:
Wei-Ning Huange0def6a2015-11-05 15:41:24 +0800325 with contextlib.closing(
Yilin Yangf54fb912020-01-08 11:42:38 +0800326 urllib.request.urlopen(url, timeout=_CONNECT_TIMEOUT,
327 context=self._tls_settings.Context())) as f:
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800328 if f.getcode() != 200:
329 raise RuntimeError('HTTP status %d' % f.getcode())
330 data = f.read()
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800331 except (ssl.SSLError, ssl.CertificateError) as e:
332 logging.error('Upgrade: %s: %s', e.__class__.__name__, e)
333 return
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800334 except Exception:
335 logging.error('Upgrade: failed to download upgrade, abort')
336 return
337
338 # Compare SHA1 sum
339 if hashlib.sha1(data).hexdigest() != sha1sum:
340 logging.error('Upgrade: sha1sum mismatch, abort')
341 return
342
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800343 try:
Yilin Yang235e5982019-12-26 10:36:22 +0800344 with open(scriptpath, 'wb') as f:
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800345 f.write(data)
346 except Exception:
347 logging.error('Upgrade: failed to write upgrade onto disk, abort')
348 return
349
350 logging.info('Upgrade: restarting ghost...')
351 self.CloseSockets()
352 self.SetIgnoreChild(False)
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +0800353 os.execve(scriptpath, [scriptpath] + sys.argv[1:], os.environ)
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800354
Wei-Ning Huang23ed0162015-09-18 14:42:03 +0800355 def LoadProperties(self):
Wei-Ning Huang7d029b12015-03-06 10:32:15 +0800356 try:
Wei-Ning Huang23ed0162015-09-18 14:42:03 +0800357 if self._prop_file:
358 with open(self._prop_file, 'r') as f:
359 self._properties = json.loads(f.read())
Wei-Ning Huang7d029b12015-03-06 10:32:15 +0800360 except Exception as e:
Peter Shih769b0772018-02-26 14:44:28 +0800361 logging.error('LoadProperties: %s', e)
Wei-Ning Huang7d029b12015-03-06 10:32:15 +0800362
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800363 def CloseSockets(self):
364 # Close sockets opened by parent process, since we don't use it anymore.
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800365 if self._platform == 'Linux':
366 for fd in os.listdir('/proc/self/fd/'):
367 try:
368 real_fd = os.readlink('/proc/self/fd/%s' % fd)
369 if real_fd.startswith('socket'):
370 os.close(int(fd))
371 except Exception:
372 pass
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800373
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800374 def SpawnGhost(self, mode, sid=None, terminal_sid=None, tty_device=None,
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800375 command=None, file_op=None, port=None):
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800376 """Spawn a child ghost with specific mode.
377
378 Returns:
379 The spawned child process pid.
380 """
Joel Kitching22b89042015-08-06 18:23:29 +0800381 # Restore the default signal handler, so our child won't have problems.
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800382 self.SetIgnoreChild(False)
383
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800384 pid = os.fork()
385 if pid == 0:
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800386 self.CloseSockets()
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800387 g = Ghost([self._connected_addr], tls_settings=self._tls_settings,
388 mode=mode, mid=Ghost.RANDOM_MID, sid=sid,
Wei-Ning Huang23ed0162015-09-18 14:42:03 +0800389 terminal_sid=terminal_sid, tty_device=tty_device,
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800390 command=command, file_op=file_op, port=port)
Wei-Ning Huang2132de32015-04-13 17:24:38 +0800391 g.Start()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800392 sys.exit(0)
393 else:
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800394 self.SetIgnoreChild(True)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800395 return pid
396
397 def Timestamp(self):
398 return int(time.time())
399
400 def GetGateWayIP(self):
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800401 if self._platform == 'Darwin':
Yilin Yang42ba5c62020-05-05 10:32:34 +0800402 output = process_utils.CheckOutput(['route', '-n', 'get', 'default'])
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800403 ret = re.search('gateway: (.*)', output)
404 if ret:
405 return [ret.group(1)]
Peter Shiha78867d2018-02-26 14:17:51 +0800406 return []
Fei Shao12ecf382020-06-23 18:32:26 +0800407 if self._platform == 'Linux':
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800408 with open('/proc/net/route', 'r') as f:
409 lines = f.readlines()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800410
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800411 ips = []
412 for line in lines:
413 parts = line.split('\t')
414 if parts[2] == '00000000':
415 continue
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800416
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800417 try:
Yilin Yangf9fe1932019-11-04 17:09:34 +0800418 h = codecs.decode(parts[2], 'hex')
Yilin Yangacd3c792020-05-05 10:00:30 +0800419 ips.append('.'.join([str(x) for x in reversed(h)]))
Yilin Yangf9fe1932019-11-04 17:09:34 +0800420 except (TypeError, binascii.Error):
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800421 pass
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800422
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800423 return ips
Fei Shao12ecf382020-06-23 18:32:26 +0800424
425 logging.warning('GetGateWayIP: unsupported platform')
426 return []
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800427
Hung-Te Lin41ff8f32017-08-30 08:10:39 +0800428 def GetFactoryServerIP(self):
Wei-Ning Huang829e0c82015-05-26 14:37:23 +0800429 try:
Hung-Te Lin41ff8f32017-08-30 08:10:39 +0800430 from cros.factory.test import server_proxy
Wei-Ning Huang829e0c82015-05-26 14:37:23 +0800431
Hung-Te Lin41ff8f32017-08-30 08:10:39 +0800432 url = server_proxy.GetServerURL()
Wei-Ning Huang829e0c82015-05-26 14:37:23 +0800433 match = re.match(r'^https?://(.*):.*$', url)
434 if match:
435 return [match.group(1)]
436 except Exception:
437 pass
438 return []
439
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800440 def GetMachineID(self):
441 """Generates machine-dependent ID string for a machine.
442 There are many ways to generate a machine ID:
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800443 Linux:
444 1. factory device_id
Peter Shih5f1f48c2017-06-26 14:12:00 +0800445 2. /sys/class/dmi/id/product_uuid (only available on intel machines)
446 3. MAC address
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800447 We follow the listed order to generate machine ID, and fallback to the
448 next alternative if the previous doesn't work.
449
450 Darwin:
451 All Darwin system should have the IOPlatformSerialNumber attribute.
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800452 """
Wei-Ning Huangc9c97f02015-05-19 15:05:42 +0800453 if self._mid == Ghost.RANDOM_MID:
Wei-Ning Huangaed90452015-03-23 17:50:21 +0800454 return str(uuid.uuid4())
Fei Shao12ecf382020-06-23 18:32:26 +0800455 if self._mid:
Wei-Ning Huangc9c97f02015-05-19 15:05:42 +0800456 return self._mid
Wei-Ning Huangaed90452015-03-23 17:50:21 +0800457
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800458 # Darwin
459 if self._platform == 'Darwin':
Yilin Yang42ba5c62020-05-05 10:32:34 +0800460 output = process_utils.CheckOutput(['ioreg', '-rd1', '-c',
461 'IOPlatformExpertDevice'])
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800462 ret = re.search('"IOPlatformSerialNumber" = "(.*)"', output)
463 if ret:
464 return ret.group(1)
465
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800466 # Try factory device id
467 try:
Hung-Te Linda8eb992017-09-28 03:27:12 +0800468 from cros.factory.test import session
469 return session.GetDeviceID()
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800470 except Exception:
471 pass
472
Wei-Ning Huang1d7603b2015-07-03 17:38:56 +0800473 # Try DMI product UUID
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800474 try:
475 with open('/sys/class/dmi/id/product_uuid', 'r') as f:
476 return f.read().strip()
477 except Exception:
478 pass
479
Wei-Ning Huang1d7603b2015-07-03 17:38:56 +0800480 # Use MAC address if non is available
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800481 try:
482 macs = []
483 ifaces = sorted(os.listdir('/sys/class/net'))
484 for iface in ifaces:
485 if iface == 'lo':
486 continue
487
488 with open('/sys/class/net/%s/address' % iface, 'r') as f:
489 macs.append(f.read().strip())
490
491 return ';'.join(macs)
492 except Exception:
493 pass
494
Peter Shihcb0e5512017-06-14 16:59:46 +0800495 raise RuntimeError("can't generate machine ID")
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800496
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800497 def GetProcessWorkingDirectory(self, pid):
498 if self._platform == 'Linux':
499 return os.readlink('/proc/%d/cwd' % pid)
Fei Shao12ecf382020-06-23 18:32:26 +0800500 if self._platform == 'Darwin':
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800501 PROC_PIDVNODEPATHINFO = 9
502 proc_vnodepathinfo_size = 2352
503 vid_path_offset = 152
504
505 proc = ctypes.cdll.LoadLibrary(ctypes.util.find_library('libproc'))
506 buf = ctypes.create_string_buffer('\0' * proc_vnodepathinfo_size)
507 proc.proc_pidinfo(pid, PROC_PIDVNODEPATHINFO, 0,
508 ctypes.byref(buf), proc_vnodepathinfo_size)
509 buf = buf.raw[vid_path_offset:]
510 n = buf.index('\0')
511 return buf[:n]
Fei Shao12ecf382020-06-23 18:32:26 +0800512 raise RuntimeError('GetProcessWorkingDirectory: unsupported platform')
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800513
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800514 def Reset(self):
515 """Reset state and clear request handlers."""
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800516 if self._sock is not None:
517 self._sock.Close()
518 self._sock = None
Wei-Ning Huang2132de32015-04-13 17:24:38 +0800519 self._reset.clear()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800520 self._last_ping = 0
521 self._requests = {}
Wei-Ning Huang23ed0162015-09-18 14:42:03 +0800522 self.LoadProperties()
Wei-Ning Huang7ec55342015-09-17 08:46:06 +0800523 self._register_status = DISCONNECTED
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800524
525 def SendMessage(self, msg):
526 """Serialize the message and send it through the socket."""
Yilin Yang6b9ec9d2019-12-09 11:04:06 +0800527 self._sock.Send(json.dumps(msg).encode('utf-8') + _SEPARATOR)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800528
529 def SendRequest(self, name, args, handler=None,
530 timeout=_REQUEST_TIMEOUT_SECS):
531 if handler and not callable(handler):
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800532 raise RequestError('Invalid request handler for msg "%s"' % name)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800533
534 rid = str(uuid.uuid4())
535 msg = {'rid': rid, 'timeout': timeout, 'name': name, 'params': args}
Wei-Ning Huange2981862015-08-03 15:03:08 +0800536 if timeout >= 0:
537 self._requests[rid] = [self.Timestamp(), timeout, handler]
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800538 self.SendMessage(msg)
539
540 def SendResponse(self, omsg, status, params=None):
541 msg = {'rid': omsg['rid'], 'response': status, 'params': params}
542 self.SendMessage(msg)
543
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800544 def HandleTTYControl(self, fd, control_str):
545 msg = json.loads(control_str)
Moja Hsuc9ecc8b2015-07-13 11:39:17 +0800546 command = msg['command']
547 params = msg['params']
548 if command == 'resize':
549 # some error happened on websocket
550 if len(params) != 2:
551 return
552 winsize = struct.pack('HHHH', params[0], params[1], 0, 0)
553 fcntl.ioctl(fd, termios.TIOCSWINSZ, winsize)
554 else:
Yilin Yang9881b1e2019-12-11 11:47:33 +0800555 logging.warning('Invalid request command "%s"', command)
Moja Hsuc9ecc8b2015-07-13 11:39:17 +0800556
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800557 def SpawnTTYServer(self, unused_var):
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800558 """Spawn a TTY server and forward I/O to the TCP socket."""
559 logging.info('SpawnTTYServer: started')
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800560
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800561 try:
562 if self._tty_device is None:
563 pid, fd = os.forkpty()
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800564
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800565 if pid == 0:
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800566 ttyname = os.ttyname(sys.stdout.fileno())
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800567 try:
568 server = GhostRPCServer()
569 server.RegisterTTY(self._session_id, ttyname)
570 server.RegisterSession(self._session_id, os.getpid())
571 except Exception:
572 # If ghost is launched without RPC server, the call will fail but we
573 # can ignore it.
574 pass
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800575
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800576 # The directory that contains the current running ghost script
577 script_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800578
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800579 env = os.environ.copy()
580 env['USER'] = os.getenv('USER', 'root')
581 env['HOME'] = os.getenv('HOME', '/root')
582 env['PATH'] = os.getenv('PATH') + ':%s' % script_dir
583 os.chdir(env['HOME'])
584 os.execve(_SHELL, [_SHELL], env)
585 else:
586 fd = os.open(self._tty_device, os.O_RDWR)
Wei-Ning Huang39169902015-09-19 06:00:23 +0800587 tty.setraw(fd)
Stimim Chen3899a912020-07-17 10:52:03 +0800588 # 0: iflag
589 # 1: oflag
590 # 2: cflag
591 # 3: lflag
592 # 4: ispeed
593 # 5: ospeed
594 # 6: cc
Wei-Ning Huang39169902015-09-19 06:00:23 +0800595 attr = termios.tcgetattr(fd)
Stimim Chen3899a912020-07-17 10:52:03 +0800596 attr[0] &= (termios.IXON | termios.IXOFF)
Wei-Ning Huang39169902015-09-19 06:00:23 +0800597 attr[2] |= termios.CLOCAL
598 attr[2] &= ~termios.CRTSCTS
599 attr[4] = termios.B115200
600 attr[5] = termios.B115200
601 termios.tcsetattr(fd, termios.TCSANOW, attr)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800602
Stimim Chen3899a912020-07-17 10:52:03 +0800603 nonlocals = {
604 'control_state': None,
605 'control_str': b''
606 }
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800607
608 def _ProcessBuffer(buf):
Stimim Chen3899a912020-07-17 10:52:03 +0800609 write_buffer = b''
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800610 while buf:
611 if nonlocals['control_state']:
Stimim Chen3899a912020-07-17 10:52:03 +0800612 if _CONTROL_END in buf:
613 index = buf.index(_CONTROL_END)
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800614 nonlocals['control_str'] += buf[:index]
615 self.HandleTTYControl(fd, nonlocals['control_str'])
616 nonlocals['control_state'] = None
Stimim Chen3899a912020-07-17 10:52:03 +0800617 nonlocals['control_str'] = b''
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800618 buf = buf[index+1:]
619 else:
620 nonlocals['control_str'] += buf
Stimim Chen3899a912020-07-17 10:52:03 +0800621 buf = b''
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800622 else:
Stimim Chen3899a912020-07-17 10:52:03 +0800623 if _CONTROL_START in buf:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800624 nonlocals['control_state'] = _CONTROL_START
Stimim Chen3899a912020-07-17 10:52:03 +0800625 index = buf.index(_CONTROL_START)
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800626 write_buffer += buf[:index]
627 buf = buf[index+1:]
628 else:
629 write_buffer += buf
Stimim Chen3899a912020-07-17 10:52:03 +0800630 buf = b''
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800631
632 if write_buffer:
633 os.write(fd, write_buffer)
634
635 _ProcessBuffer(self._sock.RecvBuf())
636
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800637 while True:
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800638 rd, unused_wd, unused_xd = select.select([self._sock, fd], [], [])
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800639
640 if fd in rd:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800641 self._sock.Send(os.read(fd, _BUFSIZE))
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800642
643 if self._sock in rd:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800644 buf = self._sock.Recv(_BUFSIZE)
Peter Shihaacbc2f2017-06-16 14:39:29 +0800645 if not buf:
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +0800646 raise RuntimeError('connection terminated')
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800647 _ProcessBuffer(buf)
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800648 except Exception as e:
Stimim Chen3899a912020-07-17 10:52:03 +0800649 logging.error('SpawnTTYServer: %s', e, exc_info=True)
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800650 finally:
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800651 self._sock.Close()
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800652
653 logging.info('SpawnTTYServer: terminated')
Yilin Yanga03fd0a2020-10-23 16:58:14 +0800654 os._exit(0) # pylint: disable=protected-access
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800655
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800656 def SpawnShellServer(self, unused_var):
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +0800657 """Spawn a shell server and forward input/output from/to the TCP socket."""
658 logging.info('SpawnShellServer: started')
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800659
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +0800660 # Add ghost executable to PATH
661 script_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
662 env = os.environ.copy()
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800663 env['PATH'] = '%s:%s' % (script_dir, os.getenv('PATH'))
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +0800664
665 # Execute shell command from HOME directory
666 os.chdir(os.getenv('HOME', '/tmp'))
667
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +0800668 p = subprocess.Popen(self._shell_command, stdin=subprocess.PIPE,
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800669 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +0800670 shell=True, env=env)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800671
672 def make_non_block(fd):
673 fl = fcntl.fcntl(fd, fcntl.F_GETFL)
674 fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
675
676 make_non_block(p.stdout)
677 make_non_block(p.stderr)
678
679 try:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800680 p.stdin.write(self._sock.RecvBuf())
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800681
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800682 while True:
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800683 rd, unused_wd, unused_xd = select.select(
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800684 [p.stdout, p.stderr, self._sock], [], [])
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800685 if p.stdout in rd:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800686 self._sock.Send(p.stdout.read(_BUFSIZE))
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800687
688 if p.stderr in rd:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800689 self._sock.Send(p.stderr.read(_BUFSIZE))
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800690
691 if self._sock in rd:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800692 ret = self._sock.Recv(_BUFSIZE)
Peter Shihaacbc2f2017-06-16 14:39:29 +0800693 if not ret:
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +0800694 raise RuntimeError('connection terminated')
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800695
696 try:
697 idx = ret.index(_STDIN_CLOSED * 2)
698 p.stdin.write(ret[:idx])
699 p.stdin.close()
700 except ValueError:
701 p.stdin.write(ret)
Wei-Ning Huangf14c84e2015-08-03 15:03:08 +0800702 p.poll()
Peter Shihe6afab32018-09-11 17:16:48 +0800703 if p.returncode is not None:
Wei-Ning Huangf14c84e2015-08-03 15:03:08 +0800704 break
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800705 except Exception as e:
Stimim Chen3899a912020-07-17 10:52:03 +0800706 logging.error('SpawnShellServer: %s', e, exc_info=True)
Wei-Ning Huangf14c84e2015-08-03 15:03:08 +0800707 finally:
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800708 # Check if the process is terminated. If not, Send SIGTERM to process,
709 # then wait for 1 second. Send another SIGKILL to make sure the process is
710 # terminated.
711 p.poll()
712 if p.returncode is None:
713 try:
714 p.terminate()
715 time.sleep(1)
716 p.kill()
717 except Exception:
718 pass
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +0800719
720 p.wait()
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800721 self._sock.Close()
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800722
723 logging.info('SpawnShellServer: terminated')
Yilin Yanga03fd0a2020-10-23 16:58:14 +0800724 os._exit(0) # pylint: disable=protected-access
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800725
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800726 def InitiateFileOperation(self, unused_var):
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800727 if self._file_op[0] == 'download':
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800728 try:
729 size = os.stat(self._file_op[1]).st_size
730 except OSError as e:
731 logging.error('InitiateFileOperation: download: %s', e)
732 sys.exit(1)
733
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800734 self.SendRequest('request_to_download',
Wei-Ning Huangd521f282015-08-07 05:28:04 +0800735 {'terminal_sid': self._terminal_session_id,
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800736 'filename': os.path.basename(self._file_op[1]),
737 'size': size})
Wei-Ning Huange2981862015-08-03 15:03:08 +0800738 elif self._file_op[0] == 'upload':
739 self.SendRequest('clear_to_upload', {}, timeout=-1)
740 self.StartUploadServer()
741 else:
742 logging.error('InitiateFileOperation: unknown file operation, ignored')
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800743
744 def StartDownloadServer(self):
745 logging.info('StartDownloadServer: started')
746
747 try:
748 with open(self._file_op[1], 'rb') as f:
749 while True:
750 data = f.read(_BLOCK_SIZE)
Peter Shihaacbc2f2017-06-16 14:39:29 +0800751 if not data:
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800752 break
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800753 self._sock.Send(data)
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800754 except Exception as e:
755 logging.error('StartDownloadServer: %s', e)
756 finally:
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800757 self._sock.Close()
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800758
759 logging.info('StartDownloadServer: terminated')
760 sys.exit(0)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800761
Wei-Ning Huange2981862015-08-03 15:03:08 +0800762 def StartUploadServer(self):
763 logging.info('StartUploadServer: started')
Wei-Ning Huange2981862015-08-03 15:03:08 +0800764 try:
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800765 filepath = self._file_op[1]
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800766 dirname = os.path.dirname(filepath)
767 if not os.path.exists(dirname):
768 try:
769 os.makedirs(dirname)
770 except Exception:
771 pass
Wei-Ning Huange2981862015-08-03 15:03:08 +0800772
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800773 with open(filepath, 'wb') as f:
Wei-Ning Huang8ee3bcd2015-10-01 17:10:01 +0800774 if self._file_op[2]:
775 os.fchmod(f.fileno(), self._file_op[2])
776
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800777 f.write(self._sock.RecvBuf())
778
Wei-Ning Huange2981862015-08-03 15:03:08 +0800779 while True:
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800780 rd, unused_wd, unused_xd = select.select([self._sock], [], [])
Wei-Ning Huange2981862015-08-03 15:03:08 +0800781 if self._sock in rd:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800782 buf = self._sock.Recv(_BLOCK_SIZE)
Peter Shihaacbc2f2017-06-16 14:39:29 +0800783 if not buf:
Wei-Ning Huange2981862015-08-03 15:03:08 +0800784 break
785 f.write(buf)
786 except socket.error as e:
787 logging.error('StartUploadServer: socket error: %s', e)
788 except Exception as e:
789 logging.error('StartUploadServer: %s', e)
790 finally:
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800791 self._sock.Close()
Wei-Ning Huange2981862015-08-03 15:03:08 +0800792
793 logging.info('StartUploadServer: terminated')
Yilin Yanga03fd0a2020-10-23 16:58:14 +0800794 os._exit(0) # pylint: disable=protected-access
Wei-Ning Huange2981862015-08-03 15:03:08 +0800795
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800796 def SpawnPortForwardServer(self, unused_var):
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800797 """Spawn a port forwarding server and forward I/O to the TCP socket."""
798 logging.info('SpawnPortForwardServer: started')
799
800 src_sock = None
801 try:
802 src_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Wei-Ning Huange0def6a2015-11-05 15:41:24 +0800803 src_sock.settimeout(_CONNECT_TIMEOUT)
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800804 src_sock.connect(('localhost', self._port))
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800805
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800806 src_sock.send(self._sock.RecvBuf())
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800807
808 while True:
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800809 rd, unused_wd, unused_xd = select.select([self._sock, src_sock], [], [])
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800810
811 if self._sock in rd:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800812 data = self._sock.Recv(_BUFSIZE)
Peter Shihaacbc2f2017-06-16 14:39:29 +0800813 if not data:
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +0800814 raise RuntimeError('connection terminated')
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800815 src_sock.send(data)
816
817 if src_sock in rd:
818 data = src_sock.recv(_BUFSIZE)
Peter Shihaacbc2f2017-06-16 14:39:29 +0800819 if not data:
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800820 break
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800821 self._sock.Send(data)
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800822 except Exception as e:
823 logging.error('SpawnPortForwardServer: %s', e)
824 finally:
825 if src_sock:
826 src_sock.close()
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800827 self._sock.Close()
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800828
829 logging.info('SpawnPortForwardServer: terminated')
Yilin Yanga03fd0a2020-10-23 16:58:14 +0800830 os._exit(0) # pylint: disable=protected-access
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800831
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800832 def Ping(self):
833 def timeout_handler(x):
834 if x is None:
835 raise PingTimeoutError
836
837 self._last_ping = self.Timestamp()
838 self.SendRequest('ping', {}, timeout_handler, 5)
839
Wei-Ning Huangae923642015-09-24 14:08:09 +0800840 def HandleFileDownloadRequest(self, msg):
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800841 params = msg['params']
Wei-Ning Huangae923642015-09-24 14:08:09 +0800842 filepath = params['filename']
843 if not os.path.isabs(filepath):
844 filepath = os.path.join(os.getenv('HOME', '/tmp'), filepath)
845
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800846 try:
Wei-Ning Huang11c35022015-10-21 16:52:32 +0800847 with open(filepath, 'r') as _:
Wei-Ning Huang46a3fc92015-10-06 02:35:27 +0800848 pass
849 except Exception as e:
Peter Shiha78867d2018-02-26 14:17:51 +0800850 self.SendResponse(msg, str(e))
851 return
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800852
853 self.SpawnGhost(self.FILE, params['sid'],
Wei-Ning Huangae923642015-09-24 14:08:09 +0800854 file_op=('download', filepath))
855 self.SendResponse(msg, SUCCESS)
856
857 def HandleFileUploadRequest(self, msg):
858 params = msg['params']
859
860 # Resolve upload filepath
861 filename = params['filename']
862 dest_path = filename
863
864 # If dest is specified, use it first
865 dest_path = params.get('dest', '')
866 if dest_path:
867 if not os.path.isabs(dest_path):
868 dest_path = os.path.join(os.getenv('HOME', '/tmp'), dest_path)
869
870 if os.path.isdir(dest_path):
871 dest_path = os.path.join(dest_path, filename)
872 else:
873 target_dir = os.getenv('HOME', '/tmp')
874
875 # Terminal session ID found, upload to it's current working directory
Peter Shihe6afab32018-09-11 17:16:48 +0800876 if 'terminal_sid' in params:
Wei-Ning Huangae923642015-09-24 14:08:09 +0800877 pid = self._terminal_sid_to_pid.get(params['terminal_sid'], None)
878 if pid:
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800879 try:
880 target_dir = self.GetProcessWorkingDirectory(pid)
881 except Exception as e:
882 logging.error(e)
Wei-Ning Huangae923642015-09-24 14:08:09 +0800883
884 dest_path = os.path.join(target_dir, filename)
885
886 try:
887 os.makedirs(os.path.dirname(dest_path))
888 except Exception:
889 pass
890
891 try:
892 with open(dest_path, 'w') as _:
893 pass
894 except Exception as e:
Peter Shiha78867d2018-02-26 14:17:51 +0800895 self.SendResponse(msg, str(e))
896 return
Wei-Ning Huangae923642015-09-24 14:08:09 +0800897
Wei-Ning Huangd6f69762015-10-01 21:02:07 +0800898 # If not check_only, spawn FILE mode ghost agent to handle upload
899 if not params.get('check_only', False):
900 self.SpawnGhost(self.FILE, params['sid'],
901 file_op=('upload', dest_path, params.get('perm', None)))
Wei-Ning Huang7ec55342015-09-17 08:46:06 +0800902 self.SendResponse(msg, SUCCESS)
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800903
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800904 def HandleRequest(self, msg):
Wei-Ning Huange2981862015-08-03 15:03:08 +0800905 command = msg['name']
906 params = msg['params']
907
908 if command == 'upgrade':
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800909 self.Upgrade()
Wei-Ning Huange2981862015-08-03 15:03:08 +0800910 elif command == 'terminal':
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800911 self.SpawnGhost(self.TERMINAL, params['sid'],
912 tty_device=params['tty_device'])
Wei-Ning Huang7ec55342015-09-17 08:46:06 +0800913 self.SendResponse(msg, SUCCESS)
Wei-Ning Huange2981862015-08-03 15:03:08 +0800914 elif command == 'shell':
915 self.SpawnGhost(self.SHELL, params['sid'], command=params['command'])
Wei-Ning Huang7ec55342015-09-17 08:46:06 +0800916 self.SendResponse(msg, SUCCESS)
Wei-Ning Huange2981862015-08-03 15:03:08 +0800917 elif command == 'file_download':
Wei-Ning Huangae923642015-09-24 14:08:09 +0800918 self.HandleFileDownloadRequest(msg)
Wei-Ning Huange2981862015-08-03 15:03:08 +0800919 elif command == 'clear_to_download':
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800920 self.StartDownloadServer()
Wei-Ning Huange2981862015-08-03 15:03:08 +0800921 elif command == 'file_upload':
Wei-Ning Huangae923642015-09-24 14:08:09 +0800922 self.HandleFileUploadRequest(msg)
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800923 elif command == 'forward':
924 self.SpawnGhost(self.FORWARD, params['sid'], port=params['port'])
925 self.SendResponse(msg, SUCCESS)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800926
927 def HandleResponse(self, response):
928 rid = str(response['rid'])
929 if rid in self._requests:
930 handler = self._requests[rid][2]
931 del self._requests[rid]
932 if callable(handler):
933 handler(response)
934 else:
Joel Kitching22b89042015-08-06 18:23:29 +0800935 logging.warning('Received unsolicited response, ignored')
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800936
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800937 def ParseMessage(self, buf, single=True):
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800938 if single:
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800939 try:
940 index = buf.index(_SEPARATOR)
941 except ValueError:
942 self._sock.UnRecv(buf)
943 return
944
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800945 msgs_json = [buf[:index]]
946 self._sock.UnRecv(buf[index + 2:])
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800947 else:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800948 msgs_json = buf.split(_SEPARATOR)
949 self._sock.UnRecv(msgs_json.pop())
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800950
951 for msg_json in msgs_json:
952 try:
953 msg = json.loads(msg_json)
954 except ValueError:
955 # Ignore mal-formed message.
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800956 logging.error('mal-formed JSON request, ignored')
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800957 continue
958
959 if 'name' in msg:
960 self.HandleRequest(msg)
961 elif 'response' in msg:
962 self.HandleResponse(msg)
963 else: # Ingnore mal-formed message.
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800964 logging.error('mal-formed JSON request, ignored')
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800965
966 def ScanForTimeoutRequests(self):
Joel Kitching22b89042015-08-06 18:23:29 +0800967 """Scans for pending requests which have timed out.
968
969 If any timed-out requests are discovered, their handler is called with the
970 special response value of None.
971 """
Yilin Yang78fa12e2019-09-25 14:21:10 +0800972 for rid in list(self._requests):
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800973 request_time, timeout, handler = self._requests[rid]
974 if self.Timestamp() - request_time > timeout:
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800975 if callable(handler):
976 handler(None)
977 else:
978 logging.error('Request %s timeout', rid)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800979 del self._requests[rid]
980
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800981 def InitiateDownload(self):
982 ttyname, filename = self._download_queue.get()
Wei-Ning Huangd521f282015-08-07 05:28:04 +0800983 sid = self._ttyname_to_sid[ttyname]
984 self.SpawnGhost(self.FILE, terminal_sid=sid,
Wei-Ning Huangae923642015-09-24 14:08:09 +0800985 file_op=('download', filename))
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800986
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800987 def Listen(self):
988 try:
989 while True:
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800990 rds, unused_wd, unused_xd = select.select([self._sock], [], [],
Yilin Yang14d02a22019-11-01 11:32:03 +0800991 _PING_INTERVAL // 2)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800992
993 if self._sock in rds:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800994 data = self._sock.Recv(_BUFSIZE)
Wei-Ning Huang09c19612015-11-24 16:29:09 +0800995
996 # Socket is closed
Peter Shihaacbc2f2017-06-16 14:39:29 +0800997 if not data:
Wei-Ning Huang09c19612015-11-24 16:29:09 +0800998 break
999
Wei-Ning Huanga28cd232016-01-27 15:04:41 +08001000 self.ParseMessage(data, self._register_status != SUCCESS)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001001
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001002 if (self._mode == self.AGENT and
1003 self.Timestamp() - self._last_ping > _PING_INTERVAL):
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001004 self.Ping()
1005 self.ScanForTimeoutRequests()
1006
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001007 if not self._download_queue.empty():
1008 self.InitiateDownload()
1009
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001010 if self._reset.is_set():
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001011 break
1012 except socket.error:
1013 raise RuntimeError('Connection dropped')
1014 except PingTimeoutError:
1015 raise RuntimeError('Connection timeout')
1016 finally:
Wei-Ning Huangf5311a02016-02-04 15:23:46 +08001017 self.Reset()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001018
1019 self._queue.put('resume')
1020
1021 if self._mode != Ghost.AGENT:
1022 sys.exit(1)
1023
1024 def Register(self):
1025 non_local = {}
1026 for addr in self._overlord_addrs:
1027 non_local['addr'] = addr
1028 def registered(response):
1029 if response is None:
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001030 self._reset.set()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001031 raise RuntimeError('Register request timeout')
Wei-Ning Huang63c16092015-09-18 16:20:27 +08001032
Wei-Ning Huang7ec55342015-09-17 08:46:06 +08001033 self._register_status = response['response']
1034 if response['response'] != SUCCESS:
1035 self._reset.set()
Peter Shih220a96d2016-12-22 17:02:16 +08001036 raise RuntimeError('Register: ' + response['response'])
Fei Shao0e4e2c62020-06-23 18:22:26 +08001037
1038 logging.info('Registered with Overlord at %s:%d', *non_local['addr'])
1039 self._connected_addr = non_local['addr']
1040 self.Upgrade() # Check for upgrade
1041 self._queue.put('pause', True)
Wei-Ning Huang63c16092015-09-18 16:20:27 +08001042
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001043 try:
1044 logging.info('Trying %s:%d ...', *addr)
1045 self.Reset()
Wei-Ning Huangf5311a02016-02-04 15:23:46 +08001046
Peter Shih220a96d2016-12-22 17:02:16 +08001047 # Check if server has TLS enabled. Only check if self._tls_mode is
1048 # None.
Wei-Ning Huangb6605d22016-06-22 17:33:37 +08001049 # Only control channel needs to determine if TLS is enabled. Other mode
1050 # should use the TLSSettings passed in when it was spawned.
1051 if self._mode == Ghost.AGENT:
Peter Shih220a96d2016-12-22 17:02:16 +08001052 self._tls_settings.SetEnabled(
1053 self.TLSEnabled(*addr) if self._tls_mode is None
1054 else self._tls_mode)
Wei-Ning Huang47c79b82016-05-24 01:24:46 +08001055
Wei-Ning Huangf5311a02016-02-04 15:23:46 +08001056 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1057 sock.settimeout(_CONNECT_TIMEOUT)
1058
1059 try:
1060 if self._tls_settings.Enabled():
1061 tls_context = self._tls_settings.Context()
1062 sock = tls_context.wrap_socket(sock, server_hostname=addr[0])
1063
1064 sock.connect(addr)
1065 except (ssl.SSLError, ssl.CertificateError) as e:
1066 logging.error('%s: %s', e.__class__.__name__, e)
1067 continue
1068 except IOError as e:
1069 if e.errno == 2: # No such file or directory
1070 logging.error('%s: %s', e.__class__.__name__, e)
1071 continue
1072 raise
1073
1074 self._sock = BufferedSocket(sock)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001075
1076 logging.info('Connection established, registering...')
1077 handler = {
1078 Ghost.AGENT: registered,
Wei-Ning Huangb8461202015-09-01 20:07:41 +08001079 Ghost.TERMINAL: self.SpawnTTYServer,
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001080 Ghost.SHELL: self.SpawnShellServer,
1081 Ghost.FILE: self.InitiateFileOperation,
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +08001082 Ghost.FORWARD: self.SpawnPortForwardServer,
Peter Shihe6afab32018-09-11 17:16:48 +08001083 }[self._mode]
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001084
1085 # Machine ID may change if MAC address is used (USB-ethernet dongle
1086 # plugged/unplugged)
1087 self._machine_id = self.GetMachineID()
Wei-Ning Huang7d029b12015-03-06 10:32:15 +08001088 self.SendRequest('register',
1089 {'mode': self._mode, 'mid': self._machine_id,
Wei-Ning Huangfed95862015-08-07 03:17:11 +08001090 'sid': self._session_id,
Wei-Ning Huang7d029b12015-03-06 10:32:15 +08001091 'properties': self._properties}, handler)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001092 except socket.error:
1093 pass
1094 else:
Wei-Ning Huangf5311a02016-02-04 15:23:46 +08001095 sock.settimeout(None)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001096 self.Listen()
1097
Moja Hsuc9ecc8b2015-07-13 11:39:17 +08001098 raise RuntimeError('Cannot connect to any server')
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001099
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001100 def Reconnect(self):
1101 logging.info('Received reconnect request from RPC server, reconnecting...')
1102 self._reset.set()
1103
Wei-Ning Huang7ec55342015-09-17 08:46:06 +08001104 def GetStatus(self):
Peter Shih5cafebb2017-06-30 16:36:22 +08001105 status = self._register_status
1106 if self._register_status == SUCCESS:
1107 ip, port = self._sock.sock.getpeername()
1108 status += ' %s:%d' % (ip, port)
1109 return status
Wei-Ning Huang7ec55342015-09-17 08:46:06 +08001110
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001111 def AddToDownloadQueue(self, ttyname, filename):
1112 self._download_queue.put((ttyname, filename))
1113
Wei-Ning Huangd521f282015-08-07 05:28:04 +08001114 def RegisterTTY(self, session_id, ttyname):
1115 self._ttyname_to_sid[ttyname] = session_id
Wei-Ning Huange2981862015-08-03 15:03:08 +08001116
1117 def RegisterSession(self, session_id, process_id):
1118 self._terminal_sid_to_pid[session_id] = process_id
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001119
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001120 def StartLanDiscovery(self):
1121 """Start to listen to LAN discovery packet at
1122 _OVERLORD_LAN_DISCOVERY_PORT."""
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001123
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001124 def thread_func():
1125 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
1126 s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
1127 s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001128 try:
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001129 s.bind(('0.0.0.0', _OVERLORD_LAN_DISCOVERY_PORT))
1130 except socket.error as e:
Moja Hsuc9ecc8b2015-07-13 11:39:17 +08001131 logging.error('LAN discovery: %s, abort', e)
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001132 return
1133
1134 logging.info('LAN Discovery: started')
1135 while True:
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +08001136 rd, unused_wd, unused_xd = select.select([s], [], [], 1)
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001137
1138 if s in rd:
1139 data, source_addr = s.recvfrom(_BUFSIZE)
1140 parts = data.split()
1141 if parts[0] == 'OVERLORD':
1142 ip, port = parts[1].split(':')
1143 if not ip:
1144 ip = source_addr[0]
1145 self._queue.put((ip, int(port)), True)
1146
1147 try:
1148 obj = self._queue.get(False)
Yilin Yang8b7f5192020-01-08 11:43:00 +08001149 except queue.Empty:
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001150 pass
1151 else:
Peter Shihaacbc2f2017-06-16 14:39:29 +08001152 if not isinstance(obj, str):
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001153 self._queue.put(obj)
1154 elif obj == 'pause':
1155 logging.info('LAN Discovery: paused')
1156 while obj != 'resume':
1157 obj = self._queue.get(True)
1158 logging.info('LAN Discovery: resumed')
1159
1160 t = threading.Thread(target=thread_func)
1161 t.daemon = True
1162 t.start()
1163
1164 def StartRPCServer(self):
Joel Kitching22b89042015-08-06 18:23:29 +08001165 logging.info('RPC Server: started')
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001166 rpc_server = SimpleJSONRPCServer((_DEFAULT_BIND_ADDRESS, _GHOST_RPC_PORT),
1167 logRequests=False)
1168 rpc_server.register_function(self.Reconnect, 'Reconnect')
Wei-Ning Huang7ec55342015-09-17 08:46:06 +08001169 rpc_server.register_function(self.GetStatus, 'GetStatus')
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001170 rpc_server.register_function(self.RegisterTTY, 'RegisterTTY')
Wei-Ning Huange2981862015-08-03 15:03:08 +08001171 rpc_server.register_function(self.RegisterSession, 'RegisterSession')
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001172 rpc_server.register_function(self.AddToDownloadQueue, 'AddToDownloadQueue')
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001173 t = threading.Thread(target=rpc_server.serve_forever)
1174 t.daemon = True
1175 t.start()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001176
Yilin Yang1512d972020-11-19 13:34:42 +08001177 def ApplyTestListParams(self):
1178 mgr = manager.Manager()
1179 constants = mgr.GetTestListByID(mgr.GetActiveTestListId()).constants
1180
1181 if 'overlord' not in constants:
1182 return
1183
1184 if 'overlord_urls' in constants['overlord']:
1185 for addr in [(x, _OVERLORD_PORT) for x in
1186 constants['overlord']['overlord_urls']]:
1187 if addr not in self._overlord_addrs:
1188 self._overlord_addrs.append(addr)
1189
1190 # This is sugar for ODM to turn off the verification quickly if they forgot.
1191 # So we don't support to turn on again.
1192 # If we want to turn on, we need to restart the ghost daemon.
1193 if 'tls_no_verify' in constants['overlord']:
1194 if constants['overlord']['tls_no_verify']:
1195 self._tls_settings = TLSSettings(None, False)
1196
Wei-Ning Huang829e0c82015-05-26 14:37:23 +08001197 def ScanServer(self):
Hung-Te Lin41ff8f32017-08-30 08:10:39 +08001198 for meth in [self.GetGateWayIP, self.GetFactoryServerIP]:
Wei-Ning Huang829e0c82015-05-26 14:37:23 +08001199 for addr in [(x, _OVERLORD_PORT) for x in meth()]:
1200 if addr not in self._overlord_addrs:
1201 self._overlord_addrs.append(addr)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001202
Wei-Ning Huang11c35022015-10-21 16:52:32 +08001203 def Start(self, lan_disc=False, rpc_server=False):
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001204 logging.info('%s started', self.MODE_NAME[self._mode])
1205 logging.info('MID: %s', self._machine_id)
Wei-Ning Huangfed95862015-08-07 03:17:11 +08001206 logging.info('SID: %s', self._session_id)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001207
Wei-Ning Huangb05cde32015-08-01 09:48:41 +08001208 # We don't care about child process's return code, not wait is needed. This
1209 # is used to prevent zombie process from lingering in the system.
1210 self.SetIgnoreChild(True)
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001211
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001212 if lan_disc:
1213 self.StartLanDiscovery()
1214
1215 if rpc_server:
1216 self.StartRPCServer()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001217
1218 try:
1219 while True:
1220 try:
1221 addr = self._queue.get(False)
Yilin Yang8b7f5192020-01-08 11:43:00 +08001222 except queue.Empty:
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001223 pass
1224 else:
Peter Shihaacbc2f2017-06-16 14:39:29 +08001225 if isinstance(addr, tuple) and addr not in self._overlord_addrs:
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001226 logging.info('LAN Discovery: got overlord address %s:%d', *addr)
1227 self._overlord_addrs.append(addr)
1228
Yilin Yang1512d972020-11-19 13:34:42 +08001229 if self._mode == Ghost.AGENT:
1230 self.ApplyTestListParams()
1231
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001232 try:
Wei-Ning Huang829e0c82015-05-26 14:37:23 +08001233 self.ScanServer()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001234 self.Register()
Joel Kitching22b89042015-08-06 18:23:29 +08001235 # Don't show stack trace for RuntimeError, which we use in this file for
1236 # plausible and expected errors (such as can't connect to server).
1237 except RuntimeError as e:
Yilin Yang58948af2019-10-30 18:28:55 +08001238 logging.info('%s, retrying in %ds', str(e), _RETRY_INTERVAL)
Joel Kitching22b89042015-08-06 18:23:29 +08001239 time.sleep(_RETRY_INTERVAL)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001240 except Exception as e:
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +08001241 unused_x, unused_y, exc_traceback = sys.exc_info()
Joel Kitching22b89042015-08-06 18:23:29 +08001242 traceback.print_tb(exc_traceback)
1243 logging.info('%s: %s, retrying in %ds',
Yilin Yang58948af2019-10-30 18:28:55 +08001244 e.__class__.__name__, str(e), _RETRY_INTERVAL)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001245 time.sleep(_RETRY_INTERVAL)
1246
1247 self.Reset()
1248 except KeyboardInterrupt:
1249 logging.error('Received keyboard interrupt, quit')
1250 sys.exit(0)
1251
1252
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001253def GhostRPCServer():
Wei-Ning Huang8037c182015-09-19 04:41:50 +08001254 """Returns handler to Ghost's JSON RPC server."""
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001255 return jsonrpclib.Server('http://localhost:%d' % _GHOST_RPC_PORT)
1256
1257
Wei-Ning Huang8037c182015-09-19 04:41:50 +08001258def ForkToBackground():
1259 """Fork process to run in background."""
1260 pid = os.fork()
1261 if pid != 0:
1262 logging.info('Ghost(%d) running in background.', pid)
1263 sys.exit(0)
1264
1265
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001266def DownloadFile(filename):
Wei-Ning Huang8037c182015-09-19 04:41:50 +08001267 """Initiate a client-initiated file download."""
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001268 filepath = os.path.abspath(filename)
1269 if not os.path.exists(filepath):
Joel Kitching22b89042015-08-06 18:23:29 +08001270 logging.error('file `%s\' does not exist', filename)
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001271 sys.exit(1)
1272
1273 # Check if we actually have permission to read the file
1274 if not os.access(filepath, os.R_OK):
Joel Kitching22b89042015-08-06 18:23:29 +08001275 logging.error('can not open %s for reading', filepath)
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001276 sys.exit(1)
1277
1278 server = GhostRPCServer()
1279 server.AddToDownloadQueue(os.ttyname(0), filepath)
1280 sys.exit(0)
1281
1282
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001283def main():
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +08001284 # Setup logging format
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001285 logger = logging.getLogger()
1286 logger.setLevel(logging.INFO)
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +08001287 handler = logging.StreamHandler()
1288 formatter = logging.Formatter('%(asctime)s %(message)s', '%Y/%m/%d %H:%M:%S')
1289 handler.setFormatter(formatter)
1290 logger.addHandler(handler)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001291
Wei-Ning Huang7d029b12015-03-06 10:32:15 +08001292 parser = argparse.ArgumentParser()
Wei-Ning Huang8037c182015-09-19 04:41:50 +08001293 parser.add_argument('--fork', dest='fork', action='store_true', default=False,
1294 help='fork procecess to run in background')
Wei-Ning Huangc9c97f02015-05-19 15:05:42 +08001295 parser.add_argument('--mid', metavar='MID', dest='mid', action='store',
1296 default=None, help='use MID as machine ID')
1297 parser.add_argument('--rand-mid', dest='mid', action='store_const',
1298 const=Ghost.RANDOM_MID, help='use random machine ID')
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001299 parser.add_argument('--no-lan-disc', dest='lan_disc', action='store_false',
1300 default=True, help='disable LAN discovery')
1301 parser.add_argument('--no-rpc-server', dest='rpc_server',
1302 action='store_false', default=True,
1303 help='disable RPC server')
Peter Shih220a96d2016-12-22 17:02:16 +08001304 parser.add_argument('--tls', dest='tls_mode', default='detect',
1305 choices=('y', 'n', 'detect'),
1306 help="specify 'y' or 'n' to force enable/disable TLS")
Wei-Ning Huangf5311a02016-02-04 15:23:46 +08001307 parser.add_argument('--tls-cert-file', metavar='TLS_CERT_FILE',
1308 dest='tls_cert_file', type=str, default=None,
1309 help='file containing the server TLS certificate in PEM '
1310 'format')
Wei-Ning Huang47c79b82016-05-24 01:24:46 +08001311 parser.add_argument('--tls-no-verify', dest='tls_no_verify',
1312 action='store_true', default=False,
1313 help='do not verify certificate if TLS is enabled')
Joel Kitching22b89042015-08-06 18:23:29 +08001314 parser.add_argument('--prop-file', metavar='PROP_FILE', dest='prop_file',
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001315 type=str, default=None,
Wei-Ning Huang7d029b12015-03-06 10:32:15 +08001316 help='file containing the JSON representation of client '
1317 'properties')
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001318 parser.add_argument('--download', metavar='FILE', dest='download', type=str,
1319 default=None, help='file to download')
Wei-Ning Huang23ed0162015-09-18 14:42:03 +08001320 parser.add_argument('--reset', dest='reset', default=False,
1321 action='store_true',
1322 help='reset ghost and reload all configs')
Peter Shih5cafebb2017-06-30 16:36:22 +08001323 parser.add_argument('--status', dest='status', default=False,
1324 action='store_true',
1325 help='show status of the client')
Wei-Ning Huang7d029b12015-03-06 10:32:15 +08001326 parser.add_argument('overlord_ip', metavar='OVERLORD_IP', type=str,
1327 nargs='*', help='overlord server address')
1328 args = parser.parse_args()
1329
Peter Shih5cafebb2017-06-30 16:36:22 +08001330 if args.status:
1331 print(GhostRPCServer().GetStatus())
1332 sys.exit()
1333
Wei-Ning Huang8037c182015-09-19 04:41:50 +08001334 if args.fork:
1335 ForkToBackground()
1336
Wei-Ning Huang23ed0162015-09-18 14:42:03 +08001337 if args.reset:
1338 GhostRPCServer().Reconnect()
1339 sys.exit()
1340
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001341 if args.download:
1342 DownloadFile(args.download)
1343
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001344 addrs = [('localhost', _OVERLORD_PORT)]
Wei-Ning Huang47c79b82016-05-24 01:24:46 +08001345 addrs = [(x, _OVERLORD_PORT) for x in args.overlord_ip] + addrs
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001346
Wei-Ning Huangf5311a02016-02-04 15:23:46 +08001347 prop_file = os.path.abspath(args.prop_file) if args.prop_file else None
1348
Wei-Ning Huang47c79b82016-05-24 01:24:46 +08001349 tls_settings = TLSSettings(args.tls_cert_file, not args.tls_no_verify)
Peter Shih220a96d2016-12-22 17:02:16 +08001350 tls_mode = args.tls_mode
1351 tls_mode = {'y': True, 'n': False, 'detect': None}[tls_mode]
Wei-Ning Huangf5311a02016-02-04 15:23:46 +08001352 g = Ghost(addrs, tls_settings, Ghost.AGENT, args.mid,
Peter Shih220a96d2016-12-22 17:02:16 +08001353 prop_file=prop_file, tls_mode=tls_mode)
Wei-Ning Huang11c35022015-10-21 16:52:32 +08001354 g.Start(args.lan_disc, args.rpc_server)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001355
1356
1357if __name__ == '__main__':
Wei-Ning Huangf5311a02016-02-04 15:23:46 +08001358 try:
1359 main()
1360 except Exception as e:
1361 logging.error(e)