Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 12 additions & 7 deletions sdk/python/cocoonsandbox/sandbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -381,22 +381,27 @@ def pump_out():
if not chunk:
break
local.sendall(chunk)
# a close cannot wake the read below, and the socket outlives it
with contextlib.suppress(OSError):
local.close()
local.shutdown(socket.SHUT_WR)

pump = threading.Thread(target=pump_out, daemon=True)
pump.start()
try:
while True:
chunk = local.recv(FS_CHUNK)
if not chunk:
break
guest.send(chunk)
with contextlib.suppress(OSError):
while True:
chunk = local.recv(FS_CHUNK)
if not chunk:
break
guest.send(chunk)
# half-close, not close: the guest's reply is still in flight.
guest.close_write()
with contextlib.suppress(SandboxError, OSError):
guest.close_write()
pump.join()
finally:
guest.close()
with contextlib.suppress(OSError):
local.close()

def _call(self, op: str, expect: str, **fields) -> dict:
with self._dial() as conn:
Expand Down
36 changes: 36 additions & 0 deletions sdk/python/tests/test_proxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,28 @@ def close(self) -> None:
self._closed = True


class ClosedPortConn:
"""A guest port whose upstream refuses, reported while the client is idle."""

def __init__(self):
self._dialled = False

def send(self, data: bytes) -> None:
pass

def recv(self) -> bytes:
if not self._dialled:
self._dialled = True
time.sleep(0.3)
return b""

def close_write(self) -> None:
pass

def close(self) -> None:
pass


def test_proxy_port_accepts_and_pipes(monkeypatch):
sb = Sandbox(client=Client("127.0.0.1:1"), id="sb_1", token="tok", owner="127.0.0.1:1")
monkeypatch.setattr(sb, "dial_port", lambda port: FakePortConn())
Expand All @@ -50,3 +72,17 @@ def test_proxy_port_accepts_and_pipes(monkeypatch):
c.close()
finally:
listener.close()


def test_proxy_port_ends_the_local_connection_when_the_guest_port_is_closed(monkeypatch):
sb = Sandbox(client=Client("127.0.0.1:1"), id="sb_1", token="tok", owner="127.0.0.1:1")
monkeypatch.setattr(sb, "dial_port", lambda port: ClosedPortConn())

listener = sb.proxy_port("127.0.0.1:0", 8080)
try:
c = socket.create_connection(listener.getsockname(), timeout=5)
c.settimeout(5)
assert c.recv(256) == b""
c.close()
finally:
listener.close()