fix: an answer whose client went away ends quietly, and a shell whose client left before it opened is stopped.
Release / release (push) Successful in 15s

This commit is contained in:
slawek committed 2026-10-30 02:03:58 +01:00
1 parent dd489e65aa
commit 1e0e375d60
7 files changed
+109 -44

No files matched your search

+12
View File
@@ -1,3 +1,15 @@
vpn-router (0.37.3-1) unstable; urgency=medium
fix: an answer whose client went away ends quietly, and a shell whose client left before it opened is stopped.
* The web service, the metrics service and the bootstrap server stop
writing an answer when its client closes the connection, and a site
file whose download is left no longer gets a second answer.
* A shell whose client closed the connection before it opened is
stopped and its end is logged, instead of running on.
-- Slawomir Koszewski <slawek@koszewscy.waw.pl> Fri, 30 Oct 2026 02:03:26 +0100
vpn-router (0.37.2-1) unstable; urgency=medium
fix: the web service, the metrics service and the bootstrap server log a failure in one line instead of a traceback, and nothing when a client goes away.
@@ -43,13 +43,17 @@ def report():
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
body = report().encode()
self.send_response(200)
self.send_header('Content-Type', 'text/plain; charset=utf-8')
self.send_header('Content-Length', str(len(body)))
self.send_header('Cache-Control', 'no-store')
self.send_header('Refresh', '10')
self.end_headers()
self.wfile.write(body)
try:
self.send_response(200)
self.send_header('Content-Type', 'text/plain; charset=utf-8')
self.send_header('Content-Length', str(len(body)))
self.send_header('Cache-Control', 'no-store')
self.send_header('Refresh', '10')
self.end_headers()
self.wfile.write(body)
except ConnectionError:
# The client went away before the answer was written.
self.close_connection = True
def failed(request, client_address):
+13 -9
View File
@@ -405,15 +405,19 @@ class Handler(BaseHTTPRequestHandler):
registry = CollectorRegistry()
registry.register(Sample(current))
body, kind, code = generate_latest(registry), CONTENT_TYPE_LATEST, 200
self.send_response(code)
self.send_header('Content-Type', kind)
self.send_header('Content-Length', str(len(body)))
self.send_header('Cache-Control', 'no-store')
for name, value in extra:
self.send_header(name, value)
self.end_headers()
if send_body:
self.wfile.write(body)
try:
self.send_response(code)
self.send_header('Content-Type', kind)
self.send_header('Content-Length', str(len(body)))
self.send_header('Cache-Control', 'no-store')
for name, value in extra:
self.send_header(name, value)
self.end_headers()
if send_body:
self.wfile.write(body)
except ConnectionError:
# The client went away before the answer was written.
self.close_connection = True
def do_GET(self):
self.answer(True)
@@ -188,15 +188,19 @@ class Handler(BaseHTTPRequestHandler):
code = 200
else:
body, kind, code = b'Not found\n', 'text/plain; charset=utf-8', 404
self.send_response(code)
self.send_header('Content-Type', kind)
self.send_header('Content-Length', str(len(body)))
self.send_header('Cache-Control', 'no-store')
for name, value in extra:
self.send_header(name, value)
self.end_headers()
if send_body:
self.wfile.write(body)
try:
self.send_response(code)
self.send_header('Content-Type', kind)
self.send_header('Content-Length', str(len(body)))
self.send_header('Cache-Control', 'no-store')
for name, value in extra:
self.send_header(name, value)
self.end_headers()
if send_body:
self.wfile.write(body)
except ConnectionError:
# The client went away before the answer was written.
self.close_connection = True
def raw_body(self):
"""The request body's bytes, b'' without one or over BODY_LIMIT."""
@@ -367,11 +367,14 @@ def shell(handler, name):
def refuse(code, message):
log(f'shell of {name} from {handler.client()} refused: {message}')
body = json.dumps({'error': message}).encode() + b'\n'
handler.send_response(code)
handler.send_header('Content-Type', 'application/json')
handler.send_header('Content-Length', str(len(body)))
handler.end_headers()
handler.wfile.write(body)
try:
handler.send_response(code)
handler.send_header('Content-Type', 'application/json')
handler.send_header('Content-Length', str(len(body)))
handler.end_headers()
handler.wfile.write(body)
except ConnectionError:
handler.close_connection = True
key = handler.headers.get('Sec-WebSocket-Key', '').strip()
if handler.headers.get('Upgrade', '').lower() != 'websocket' or 'upgrade' not in handler.headers.get('Connection', '').lower() \
@@ -408,12 +411,21 @@ def shell(handler, name):
finally:
os.close(terminal)
accept = base64.b64encode(hashlib.sha1((key + WEBSOCKET_GUID).encode()).digest()).decode()
handler.send_response(101, 'Switching Protocols')
handler.send_header('Upgrade', 'websocket')
handler.send_header('Connection', 'Upgrade')
handler.send_header('Sec-WebSocket-Accept', accept)
handler.end_headers()
handler.close_connection = True
try:
handler.send_response(101, 'Switching Protocols')
handler.send_header('Upgrade', 'websocket')
handler.send_header('Connection', 'Upgrade')
handler.send_header('Sec-WebSocket-Accept', accept)
handler.end_headers()
except ConnectionError:
# The client went away before the shell opened; the shell started for it is stopped.
run('systemctl', 'stop', unit)
os.close(master)
process.kill()
process.wait()
log(f'shell of {name} from {handler.client()} ended before it opened: the client closed the connection')
return
client, started, idle = handler.client(), time.monotonic(), options['shell_idle_minutes'] * 60
log(f'shell of {name} from {client} opened as {account.pw_name} in {unit}')
@@ -112,15 +112,19 @@ def site_files(handler, name, relative, query, payload, send_body):
return 404, {'error': f'{relative} does not exist'}
with open(target, 'rb') as handle:
size = os.fstat(handle.fileno()).st_size
handler.send_response(200)
handler.send_header('Content-Type', mimetypes.guess_type(target.name)[0] or 'application/octet-stream')
handler.send_header('Content-Length', str(size))
handler.send_header('Content-Disposition',
f"attachment; filename*=UTF-8''{urllib.parse.quote(target.name)}")
handler.send_header('Cache-Control', 'no-store')
handler.end_headers()
if send_body:
shutil.copyfileobj(handle, handler.wfile, vpnrouter_sites.CHUNK)
try:
handler.send_response(200)
handler.send_header('Content-Type', mimetypes.guess_type(target.name)[0] or 'application/octet-stream')
handler.send_header('Content-Length', str(size))
handler.send_header('Content-Disposition',
f"attachment; filename*=UTF-8''{urllib.parse.quote(target.name)}")
handler.send_header('Cache-Control', 'no-store')
handler.end_headers()
if send_body:
shutil.copyfileobj(handle, handler.wfile, vpnrouter_sites.CHUNK)
except ConnectionError:
# The client went away before the file was sent.
handler.close_connection = True
return None
if handler.command == 'PUT':
length = handler.headers.get('Content-Length')