1*6236dae4SAndroid Build Coastguard Worker#!/usr/bin/env python3 2*6236dae4SAndroid Build Coastguard Worker# -*- coding: utf-8 -*- 3*6236dae4SAndroid Build Coastguard Worker#*************************************************************************** 4*6236dae4SAndroid Build Coastguard Worker# _ _ ____ _ 5*6236dae4SAndroid Build Coastguard Worker# Project ___| | | | _ \| | 6*6236dae4SAndroid Build Coastguard Worker# / __| | | | |_) | | 7*6236dae4SAndroid Build Coastguard Worker# | (__| |_| | _ <| |___ 8*6236dae4SAndroid Build Coastguard Worker# \___|\___/|_| \_\_____| 9*6236dae4SAndroid Build Coastguard Worker# 10*6236dae4SAndroid Build Coastguard Worker# Copyright (C) Daniel Stenberg, <[email protected]>, et al. 11*6236dae4SAndroid Build Coastguard Worker# 12*6236dae4SAndroid Build Coastguard Worker# This software is licensed as described in the file COPYING, which 13*6236dae4SAndroid Build Coastguard Worker# you should have received as part of this distribution. The terms 14*6236dae4SAndroid Build Coastguard Worker# are also available at https://curl.se/docs/copyright.html. 15*6236dae4SAndroid Build Coastguard Worker# 16*6236dae4SAndroid Build Coastguard Worker# You may opt to use, copy, modify, merge, publish, distribute and/or sell 17*6236dae4SAndroid Build Coastguard Worker# copies of the Software, and permit persons to whom the Software is 18*6236dae4SAndroid Build Coastguard Worker# furnished to do so, under the terms of the COPYING file. 19*6236dae4SAndroid Build Coastguard Worker# 20*6236dae4SAndroid Build Coastguard Worker# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 21*6236dae4SAndroid Build Coastguard Worker# KIND, either express or implied. 22*6236dae4SAndroid Build Coastguard Worker# 23*6236dae4SAndroid Build Coastguard Worker# SPDX-License-Identifier: curl 24*6236dae4SAndroid Build Coastguard Worker# 25*6236dae4SAndroid Build Coastguard Worker########################################################################### 26*6236dae4SAndroid Build Coastguard Worker# 27*6236dae4SAndroid Build Coastguard Workerimport logging 28*6236dae4SAndroid Build Coastguard Workerimport os 29*6236dae4SAndroid Build Coastguard Workerimport signal 30*6236dae4SAndroid Build Coastguard Workerimport subprocess 31*6236dae4SAndroid Build Coastguard Workerimport time 32*6236dae4SAndroid Build Coastguard Workerfrom typing import Optional 33*6236dae4SAndroid Build Coastguard Workerfrom datetime import datetime, timedelta 34*6236dae4SAndroid Build Coastguard Worker 35*6236dae4SAndroid Build Coastguard Workerfrom .env import Env 36*6236dae4SAndroid Build Coastguard Workerfrom .curl import CurlClient 37*6236dae4SAndroid Build Coastguard Worker 38*6236dae4SAndroid Build Coastguard Worker 39*6236dae4SAndroid Build Coastguard Workerlog = logging.getLogger(__name__) 40*6236dae4SAndroid Build Coastguard Worker 41*6236dae4SAndroid Build Coastguard Worker 42*6236dae4SAndroid Build Coastguard Workerclass Nghttpx: 43*6236dae4SAndroid Build Coastguard Worker 44*6236dae4SAndroid Build Coastguard Worker def __init__(self, env: Env, port: int, https_port: int, name: str): 45*6236dae4SAndroid Build Coastguard Worker self.env = env 46*6236dae4SAndroid Build Coastguard Worker self._name = name 47*6236dae4SAndroid Build Coastguard Worker self._port = port 48*6236dae4SAndroid Build Coastguard Worker self._https_port = https_port 49*6236dae4SAndroid Build Coastguard Worker self._cmd = env.nghttpx 50*6236dae4SAndroid Build Coastguard Worker self._run_dir = os.path.join(env.gen_dir, name) 51*6236dae4SAndroid Build Coastguard Worker self._pid_file = os.path.join(self._run_dir, 'nghttpx.pid') 52*6236dae4SAndroid Build Coastguard Worker self._conf_file = os.path.join(self._run_dir, 'nghttpx.conf') 53*6236dae4SAndroid Build Coastguard Worker self._error_log = os.path.join(self._run_dir, 'nghttpx.log') 54*6236dae4SAndroid Build Coastguard Worker self._stderr = os.path.join(self._run_dir, 'nghttpx.stderr') 55*6236dae4SAndroid Build Coastguard Worker self._tmp_dir = os.path.join(self._run_dir, 'tmp') 56*6236dae4SAndroid Build Coastguard Worker self._process: Optional[subprocess.Popen] = None 57*6236dae4SAndroid Build Coastguard Worker self._rmf(self._pid_file) 58*6236dae4SAndroid Build Coastguard Worker self._rmf(self._error_log) 59*6236dae4SAndroid Build Coastguard Worker self._mkpath(self._run_dir) 60*6236dae4SAndroid Build Coastguard Worker self._write_config() 61*6236dae4SAndroid Build Coastguard Worker 62*6236dae4SAndroid Build Coastguard Worker @property 63*6236dae4SAndroid Build Coastguard Worker def https_port(self): 64*6236dae4SAndroid Build Coastguard Worker return self._https_port 65*6236dae4SAndroid Build Coastguard Worker 66*6236dae4SAndroid Build Coastguard Worker def exists(self): 67*6236dae4SAndroid Build Coastguard Worker return self._cmd and os.path.exists(self._cmd) 68*6236dae4SAndroid Build Coastguard Worker 69*6236dae4SAndroid Build Coastguard Worker def clear_logs(self): 70*6236dae4SAndroid Build Coastguard Worker self._rmf(self._error_log) 71*6236dae4SAndroid Build Coastguard Worker self._rmf(self._stderr) 72*6236dae4SAndroid Build Coastguard Worker 73*6236dae4SAndroid Build Coastguard Worker def is_running(self): 74*6236dae4SAndroid Build Coastguard Worker if self._process: 75*6236dae4SAndroid Build Coastguard Worker self._process.poll() 76*6236dae4SAndroid Build Coastguard Worker return self._process.returncode is None 77*6236dae4SAndroid Build Coastguard Worker return False 78*6236dae4SAndroid Build Coastguard Worker 79*6236dae4SAndroid Build Coastguard Worker def start_if_needed(self): 80*6236dae4SAndroid Build Coastguard Worker if not self.is_running(): 81*6236dae4SAndroid Build Coastguard Worker return self.start() 82*6236dae4SAndroid Build Coastguard Worker return True 83*6236dae4SAndroid Build Coastguard Worker 84*6236dae4SAndroid Build Coastguard Worker def start(self, wait_live=True): 85*6236dae4SAndroid Build Coastguard Worker pass 86*6236dae4SAndroid Build Coastguard Worker 87*6236dae4SAndroid Build Coastguard Worker def stop_if_running(self): 88*6236dae4SAndroid Build Coastguard Worker if self.is_running(): 89*6236dae4SAndroid Build Coastguard Worker return self.stop() 90*6236dae4SAndroid Build Coastguard Worker return True 91*6236dae4SAndroid Build Coastguard Worker 92*6236dae4SAndroid Build Coastguard Worker def stop(self, wait_dead=True): 93*6236dae4SAndroid Build Coastguard Worker self._mkpath(self._tmp_dir) 94*6236dae4SAndroid Build Coastguard Worker if self._process: 95*6236dae4SAndroid Build Coastguard Worker self._process.terminate() 96*6236dae4SAndroid Build Coastguard Worker self._process.wait(timeout=2) 97*6236dae4SAndroid Build Coastguard Worker self._process = None 98*6236dae4SAndroid Build Coastguard Worker return not wait_dead or self.wait_dead(timeout=timedelta(seconds=5)) 99*6236dae4SAndroid Build Coastguard Worker return True 100*6236dae4SAndroid Build Coastguard Worker 101*6236dae4SAndroid Build Coastguard Worker def restart(self): 102*6236dae4SAndroid Build Coastguard Worker self.stop() 103*6236dae4SAndroid Build Coastguard Worker return self.start() 104*6236dae4SAndroid Build Coastguard Worker 105*6236dae4SAndroid Build Coastguard Worker def reload(self, timeout: timedelta): 106*6236dae4SAndroid Build Coastguard Worker if self._process: 107*6236dae4SAndroid Build Coastguard Worker running = self._process 108*6236dae4SAndroid Build Coastguard Worker self._process = None 109*6236dae4SAndroid Build Coastguard Worker os.kill(running.pid, signal.SIGQUIT) 110*6236dae4SAndroid Build Coastguard Worker end_wait = datetime.now() + timeout 111*6236dae4SAndroid Build Coastguard Worker if not self.start(wait_live=False): 112*6236dae4SAndroid Build Coastguard Worker self._process = running 113*6236dae4SAndroid Build Coastguard Worker return False 114*6236dae4SAndroid Build Coastguard Worker while datetime.now() < end_wait: 115*6236dae4SAndroid Build Coastguard Worker try: 116*6236dae4SAndroid Build Coastguard Worker log.debug(f'waiting for nghttpx({running.pid}) to exit.') 117*6236dae4SAndroid Build Coastguard Worker running.wait(2) 118*6236dae4SAndroid Build Coastguard Worker log.debug(f'nghttpx({running.pid}) terminated -> {running.returncode}') 119*6236dae4SAndroid Build Coastguard Worker break 120*6236dae4SAndroid Build Coastguard Worker except subprocess.TimeoutExpired: 121*6236dae4SAndroid Build Coastguard Worker log.warning(f'nghttpx({running.pid}), not shut down yet.') 122*6236dae4SAndroid Build Coastguard Worker os.kill(running.pid, signal.SIGQUIT) 123*6236dae4SAndroid Build Coastguard Worker if datetime.now() >= end_wait: 124*6236dae4SAndroid Build Coastguard Worker log.error(f'nghttpx({running.pid}), terminate forcefully.') 125*6236dae4SAndroid Build Coastguard Worker os.kill(running.pid, signal.SIGKILL) 126*6236dae4SAndroid Build Coastguard Worker running.terminate() 127*6236dae4SAndroid Build Coastguard Worker running.wait(1) 128*6236dae4SAndroid Build Coastguard Worker return self.wait_live(timeout=timedelta(seconds=5)) 129*6236dae4SAndroid Build Coastguard Worker return False 130*6236dae4SAndroid Build Coastguard Worker 131*6236dae4SAndroid Build Coastguard Worker def wait_dead(self, timeout: timedelta): 132*6236dae4SAndroid Build Coastguard Worker curl = CurlClient(env=self.env, run_dir=self._tmp_dir) 133*6236dae4SAndroid Build Coastguard Worker try_until = datetime.now() + timeout 134*6236dae4SAndroid Build Coastguard Worker while datetime.now() < try_until: 135*6236dae4SAndroid Build Coastguard Worker if self._https_port > 0: 136*6236dae4SAndroid Build Coastguard Worker check_url = f'https://{self.env.domain1}:{self._https_port}/' 137*6236dae4SAndroid Build Coastguard Worker r = curl.http_get(url=check_url, extra_args=[ 138*6236dae4SAndroid Build Coastguard Worker '--trace', 'curl.trace', '--trace-time', 139*6236dae4SAndroid Build Coastguard Worker '--connect-timeout', '1' 140*6236dae4SAndroid Build Coastguard Worker ]) 141*6236dae4SAndroid Build Coastguard Worker else: 142*6236dae4SAndroid Build Coastguard Worker check_url = f'https://{self.env.domain1}:{self._port}/' 143*6236dae4SAndroid Build Coastguard Worker r = curl.http_get(url=check_url, extra_args=[ 144*6236dae4SAndroid Build Coastguard Worker '--trace', 'curl.trace', '--trace-time', 145*6236dae4SAndroid Build Coastguard Worker '--http3-only', '--connect-timeout', '1' 146*6236dae4SAndroid Build Coastguard Worker ]) 147*6236dae4SAndroid Build Coastguard Worker if r.exit_code != 0: 148*6236dae4SAndroid Build Coastguard Worker return True 149*6236dae4SAndroid Build Coastguard Worker log.debug(f'waiting for nghttpx to stop responding: {r}') 150*6236dae4SAndroid Build Coastguard Worker time.sleep(.1) 151*6236dae4SAndroid Build Coastguard Worker log.debug(f"Server still responding after {timeout}") 152*6236dae4SAndroid Build Coastguard Worker return False 153*6236dae4SAndroid Build Coastguard Worker 154*6236dae4SAndroid Build Coastguard Worker def wait_live(self, timeout: timedelta): 155*6236dae4SAndroid Build Coastguard Worker curl = CurlClient(env=self.env, run_dir=self._tmp_dir) 156*6236dae4SAndroid Build Coastguard Worker try_until = datetime.now() + timeout 157*6236dae4SAndroid Build Coastguard Worker while datetime.now() < try_until: 158*6236dae4SAndroid Build Coastguard Worker if self._https_port > 0: 159*6236dae4SAndroid Build Coastguard Worker check_url = f'https://{self.env.domain1}:{self._https_port}/' 160*6236dae4SAndroid Build Coastguard Worker r = curl.http_get(url=check_url, extra_args=[ 161*6236dae4SAndroid Build Coastguard Worker '--trace', 'curl.trace', '--trace-time', 162*6236dae4SAndroid Build Coastguard Worker '--connect-timeout', '1' 163*6236dae4SAndroid Build Coastguard Worker ]) 164*6236dae4SAndroid Build Coastguard Worker else: 165*6236dae4SAndroid Build Coastguard Worker check_url = f'https://{self.env.domain1}:{self._port}/' 166*6236dae4SAndroid Build Coastguard Worker r = curl.http_get(url=check_url, extra_args=[ 167*6236dae4SAndroid Build Coastguard Worker '--http3-only', '--trace', 'curl.trace', '--trace-time', 168*6236dae4SAndroid Build Coastguard Worker '--connect-timeout', '1' 169*6236dae4SAndroid Build Coastguard Worker ]) 170*6236dae4SAndroid Build Coastguard Worker if r.exit_code == 0: 171*6236dae4SAndroid Build Coastguard Worker return True 172*6236dae4SAndroid Build Coastguard Worker log.debug(f'waiting for nghttpx to become responsive: {r}') 173*6236dae4SAndroid Build Coastguard Worker time.sleep(.1) 174*6236dae4SAndroid Build Coastguard Worker log.error(f"Server still not responding after {timeout}") 175*6236dae4SAndroid Build Coastguard Worker return False 176*6236dae4SAndroid Build Coastguard Worker 177*6236dae4SAndroid Build Coastguard Worker def _rmf(self, path): 178*6236dae4SAndroid Build Coastguard Worker if os.path.exists(path): 179*6236dae4SAndroid Build Coastguard Worker return os.remove(path) 180*6236dae4SAndroid Build Coastguard Worker 181*6236dae4SAndroid Build Coastguard Worker def _mkpath(self, path): 182*6236dae4SAndroid Build Coastguard Worker if not os.path.exists(path): 183*6236dae4SAndroid Build Coastguard Worker return os.makedirs(path) 184*6236dae4SAndroid Build Coastguard Worker 185*6236dae4SAndroid Build Coastguard Worker def _write_config(self): 186*6236dae4SAndroid Build Coastguard Worker with open(self._conf_file, 'w') as fd: 187*6236dae4SAndroid Build Coastguard Worker fd.write('# nghttpx test config') 188*6236dae4SAndroid Build Coastguard Worker fd.write("\n".join([ 189*6236dae4SAndroid Build Coastguard Worker '# do we need something here?' 190*6236dae4SAndroid Build Coastguard Worker ])) 191*6236dae4SAndroid Build Coastguard Worker 192*6236dae4SAndroid Build Coastguard Worker 193*6236dae4SAndroid Build Coastguard Workerclass NghttpxQuic(Nghttpx): 194*6236dae4SAndroid Build Coastguard Worker 195*6236dae4SAndroid Build Coastguard Worker def __init__(self, env: Env): 196*6236dae4SAndroid Build Coastguard Worker super().__init__(env=env, name='nghttpx-quic', port=env.h3_port, 197*6236dae4SAndroid Build Coastguard Worker https_port=env.nghttpx_https_port) 198*6236dae4SAndroid Build Coastguard Worker 199*6236dae4SAndroid Build Coastguard Worker def start(self, wait_live=True): 200*6236dae4SAndroid Build Coastguard Worker self._mkpath(self._tmp_dir) 201*6236dae4SAndroid Build Coastguard Worker if self._process: 202*6236dae4SAndroid Build Coastguard Worker self.stop() 203*6236dae4SAndroid Build Coastguard Worker creds = self.env.get_credentials(self.env.domain1) 204*6236dae4SAndroid Build Coastguard Worker assert creds # convince pytype this isn't None 205*6236dae4SAndroid Build Coastguard Worker args = [ 206*6236dae4SAndroid Build Coastguard Worker self._cmd, 207*6236dae4SAndroid Build Coastguard Worker f'--frontend=*,{self.env.h3_port};quic', 208*6236dae4SAndroid Build Coastguard Worker f'--frontend=*,{self.env.nghttpx_https_port};tls', 209*6236dae4SAndroid Build Coastguard Worker f'--backend=127.0.0.1,{self.env.https_port};{self.env.domain1};sni={self.env.domain1};proto=h2;tls', 210*6236dae4SAndroid Build Coastguard Worker f'--backend=127.0.0.1,{self.env.http_port}', 211*6236dae4SAndroid Build Coastguard Worker '--log-level=INFO', 212*6236dae4SAndroid Build Coastguard Worker f'--pid-file={self._pid_file}', 213*6236dae4SAndroid Build Coastguard Worker f'--errorlog-file={self._error_log}', 214*6236dae4SAndroid Build Coastguard Worker f'--conf={self._conf_file}', 215*6236dae4SAndroid Build Coastguard Worker f'--cacert={self.env.ca.cert_file}', 216*6236dae4SAndroid Build Coastguard Worker creds.pkey_file, 217*6236dae4SAndroid Build Coastguard Worker creds.cert_file, 218*6236dae4SAndroid Build Coastguard Worker '--frontend-http3-window-size=1M', 219*6236dae4SAndroid Build Coastguard Worker '--frontend-http3-max-window-size=10M', 220*6236dae4SAndroid Build Coastguard Worker '--frontend-http3-connection-window-size=10M', 221*6236dae4SAndroid Build Coastguard Worker '--frontend-http3-max-connection-window-size=100M', 222*6236dae4SAndroid Build Coastguard Worker # f'--frontend-quic-debug-log', 223*6236dae4SAndroid Build Coastguard Worker ] 224*6236dae4SAndroid Build Coastguard Worker ngerr = open(self._stderr, 'a') 225*6236dae4SAndroid Build Coastguard Worker self._process = subprocess.Popen(args=args, stderr=ngerr) 226*6236dae4SAndroid Build Coastguard Worker if self._process.returncode is not None: 227*6236dae4SAndroid Build Coastguard Worker return False 228*6236dae4SAndroid Build Coastguard Worker return not wait_live or self.wait_live(timeout=timedelta(seconds=5)) 229*6236dae4SAndroid Build Coastguard Worker 230*6236dae4SAndroid Build Coastguard Worker 231*6236dae4SAndroid Build Coastguard Workerclass NghttpxFwd(Nghttpx): 232*6236dae4SAndroid Build Coastguard Worker 233*6236dae4SAndroid Build Coastguard Worker def __init__(self, env: Env): 234*6236dae4SAndroid Build Coastguard Worker super().__init__(env=env, name='nghttpx-fwd', port=env.h2proxys_port, 235*6236dae4SAndroid Build Coastguard Worker https_port=0) 236*6236dae4SAndroid Build Coastguard Worker 237*6236dae4SAndroid Build Coastguard Worker def start(self, wait_live=True): 238*6236dae4SAndroid Build Coastguard Worker self._mkpath(self._tmp_dir) 239*6236dae4SAndroid Build Coastguard Worker if self._process: 240*6236dae4SAndroid Build Coastguard Worker self.stop() 241*6236dae4SAndroid Build Coastguard Worker creds = self.env.get_credentials(self.env.proxy_domain) 242*6236dae4SAndroid Build Coastguard Worker assert creds # convince pytype this isn't None 243*6236dae4SAndroid Build Coastguard Worker args = [ 244*6236dae4SAndroid Build Coastguard Worker self._cmd, 245*6236dae4SAndroid Build Coastguard Worker '--http2-proxy', 246*6236dae4SAndroid Build Coastguard Worker f'--frontend=*,{self.env.h2proxys_port}', 247*6236dae4SAndroid Build Coastguard Worker f'--backend=127.0.0.1,{self.env.proxy_port}', 248*6236dae4SAndroid Build Coastguard Worker '--log-level=INFO', 249*6236dae4SAndroid Build Coastguard Worker f'--pid-file={self._pid_file}', 250*6236dae4SAndroid Build Coastguard Worker f'--errorlog-file={self._error_log}', 251*6236dae4SAndroid Build Coastguard Worker f'--conf={self._conf_file}', 252*6236dae4SAndroid Build Coastguard Worker f'--cacert={self.env.ca.cert_file}', 253*6236dae4SAndroid Build Coastguard Worker creds.pkey_file, 254*6236dae4SAndroid Build Coastguard Worker creds.cert_file, 255*6236dae4SAndroid Build Coastguard Worker ] 256*6236dae4SAndroid Build Coastguard Worker ngerr = open(self._stderr, 'a') 257*6236dae4SAndroid Build Coastguard Worker self._process = subprocess.Popen(args=args, stderr=ngerr) 258*6236dae4SAndroid Build Coastguard Worker if self._process.returncode is not None: 259*6236dae4SAndroid Build Coastguard Worker return False 260*6236dae4SAndroid Build Coastguard Worker return not wait_live or self.wait_live(timeout=timedelta(seconds=5)) 261*6236dae4SAndroid Build Coastguard Worker 262*6236dae4SAndroid Build Coastguard Worker def wait_dead(self, timeout: timedelta): 263*6236dae4SAndroid Build Coastguard Worker curl = CurlClient(env=self.env, run_dir=self._tmp_dir) 264*6236dae4SAndroid Build Coastguard Worker try_until = datetime.now() + timeout 265*6236dae4SAndroid Build Coastguard Worker while datetime.now() < try_until: 266*6236dae4SAndroid Build Coastguard Worker check_url = f'https://{self.env.proxy_domain}:{self.env.h2proxys_port}/' 267*6236dae4SAndroid Build Coastguard Worker r = curl.http_get(url=check_url) 268*6236dae4SAndroid Build Coastguard Worker if r.exit_code != 0: 269*6236dae4SAndroid Build Coastguard Worker return True 270*6236dae4SAndroid Build Coastguard Worker log.debug(f'waiting for nghttpx-fwd to stop responding: {r}') 271*6236dae4SAndroid Build Coastguard Worker time.sleep(.1) 272*6236dae4SAndroid Build Coastguard Worker log.debug(f"Server still responding after {timeout}") 273*6236dae4SAndroid Build Coastguard Worker return False 274*6236dae4SAndroid Build Coastguard Worker 275*6236dae4SAndroid Build Coastguard Worker def wait_live(self, timeout: timedelta): 276*6236dae4SAndroid Build Coastguard Worker curl = CurlClient(env=self.env, run_dir=self._tmp_dir) 277*6236dae4SAndroid Build Coastguard Worker try_until = datetime.now() + timeout 278*6236dae4SAndroid Build Coastguard Worker while datetime.now() < try_until: 279*6236dae4SAndroid Build Coastguard Worker check_url = f'https://{self.env.proxy_domain}:{self.env.h2proxys_port}/' 280*6236dae4SAndroid Build Coastguard Worker r = curl.http_get(url=check_url, extra_args=[ 281*6236dae4SAndroid Build Coastguard Worker '--trace', 'curl.trace', '--trace-time' 282*6236dae4SAndroid Build Coastguard Worker ]) 283*6236dae4SAndroid Build Coastguard Worker if r.exit_code == 0: 284*6236dae4SAndroid Build Coastguard Worker return True 285*6236dae4SAndroid Build Coastguard Worker log.debug(f'waiting for nghttpx-fwd to become responsive: {r}') 286*6236dae4SAndroid Build Coastguard Worker time.sleep(.1) 287*6236dae4SAndroid Build Coastguard Worker log.error(f"Server still not responding after {timeout}") 288*6236dae4SAndroid Build Coastguard Worker return False 289