Skip to content

Commit 9fff856

Browse files
authored
Merge pull request #29 from icgood/short
Make sure short-lived connections are proxied
2 parents b4b174d + 86afa4f commit 9fff856

2 files changed

Lines changed: 40 additions & 42 deletions

File tree

proxyprotocol/server/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ class Address:
2626
2727
"""
2828

29+
__slots__ = ['url', 'query', 'server', '_ssl']
30+
2931
def __init__(self, addr: str, *, server: bool = False) -> None:
3032
super().__init__()
3133
url = urlsplit(addr)

proxyprotocol/server/protocol.py

Lines changed: 38 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@
33

44
import logging
55
from abc import abstractmethod, ABCMeta
6-
from asyncio import Task, AbstractEventLoop, CancelledError
6+
from asyncio import Task, AbstractEventLoop
77
from asyncio.protocols import BufferedProtocol, BaseProtocol
88
from asyncio.transports import Transport, BaseTransport
99
from collections import deque
1010
from functools import partial
1111
from socket import AddressFamily, SocketKind
12-
from typing import Any, Type, Optional, Tuple, Deque, Set
12+
from typing import Type, Optional, Tuple, Deque
1313
from typing_extensions import Final
1414
from uuid import uuid4
1515

@@ -27,6 +27,8 @@
2727

2828
class _Base(BufferedProtocol, metaclass=ABCMeta):
2929

30+
__slots__ = ['_paused', '_buf', '_queue', '_transport', '_sock_info']
31+
3032
def __init__(self, buf_len: int) -> None:
3133
super().__init__()
3234
self._paused = False
@@ -41,15 +43,10 @@ def sock_info(self) -> SocketInfo:
4143
assert sock_info is not None
4244
return sock_info
4345

44-
@property
45-
def is_open(self) -> bool:
46-
return self._sock_info is not None
47-
4846
def close(self) -> None:
4947
if self._transport is not None:
5048
self._transport.close()
5149
self._transport = None
52-
self._sock_info = None
5350

5451
def write(self, data: memoryview) -> None:
5552
transport = self._transport
@@ -97,6 +94,9 @@ def proxy_data(self, data: memoryview) -> None:
9794

9895
class DownstreamProtocol(_Base):
9996

97+
__slots__ = ['loop', 'dnsbl', 'upstream', 'id', '_dnsbl_task', '_waiting',
98+
'_waiting_closed', '_upstream', '_upstream_factory']
99+
100100
def __init__(self, upstream_protocol: Type[UpstreamProtocol],
101101
loop: AbstractEventLoop, buf_len: int, dnsbl: Dnsbl,
102102
upstream: Address) -> None:
@@ -105,56 +105,57 @@ def __init__(self, upstream_protocol: Type[UpstreamProtocol],
105105
self.dnsbl: Final = dnsbl
106106
self.upstream: Final = upstream
107107
self.id: Final = uuid4().bytes
108+
self._dnsbl_task: Optional[Task[Optional[str]]] = None
108109
self._waiting: Deque[memoryview] = deque()
109-
self._tasks: Set[Task[Any]] = set()
110+
self._waiting_closed = False
110111
self._upstream: Optional[UpstreamProtocol] = None
111112
self._upstream_factory = partial(upstream_protocol, self, buf_len,
112113
upstream.pp)
113114

114-
def close(self) -> None:
115-
super().close()
116-
for task in self._tasks:
117-
task.cancel()
118-
if self._upstream is not None:
119-
upstream = self._upstream
120-
self._upstream = None
121-
upstream.close()
122-
123115
def _set_client(self, connect_task: Task[_Connect]) -> None:
116+
dnsbl_task = self._dnsbl_task
117+
assert dnsbl_task is not None
124118
try:
125119
_, upstream = connect_task.result()
126-
except CancelledError:
127-
pass # Connection was never established
128120
except OSError:
129121
self.close()
122+
dnsbl_task.cancel()
130123
_log.exception('[%s] Connection failed: %s',
131124
self.id.hex(), self.upstream)
132125
else:
133126
assert isinstance(upstream, UpstreamProtocol)
134127
self._upstream = upstream
135-
waiting = self._waiting
136-
while waiting:
137-
data = waiting.popleft()
138-
upstream.write(data)
128+
dnsbl_task.add_done_callback(self._send_initial)
129+
130+
def _send_initial(self, dnsbl_task: Task[Optional[str]]) -> None:
131+
upstream = self._upstream
132+
assert upstream is not None
133+
upstream.write_header(dnsbl_task.result())
134+
waiting = self._waiting
135+
while waiting:
136+
data = waiting.popleft()
137+
upstream.write(data)
138+
if self._waiting_closed:
139+
upstream.close()
139140

140141
def connection_made(self, transport: BaseTransport) -> None:
141142
super().connection_made(transport)
142143
_log.info('[%s] Downstream connection received: %s',
143144
self.id.hex(), self.sock_info)
144145
loop = self.loop
145-
dnsbl_task = loop.create_task(self.dnsbl.lookup(self.sock_info))
146-
self._tasks.add(dnsbl_task)
147-
dnsbl_task.add_done_callback(self._tasks.discard)
146+
self._dnsbl_task = loop.create_task(self.dnsbl.lookup(self.sock_info))
148147
connect_task = loop.create_task(
149-
loop.create_connection(partial(self._upstream_factory, dnsbl_task),
148+
loop.create_connection(self._upstream_factory,
150149
self.upstream.host, self.upstream.port or 0,
151150
ssl=self.upstream.ssl))
152-
self._tasks.add(connect_task)
153-
connect_task.add_done_callback(self._tasks.discard)
154151
connect_task.add_done_callback(self._set_client)
155152

156153
def connection_lost(self, exc: Optional[Exception]) -> None:
157154
super().connection_lost(exc)
155+
if self._upstream is None:
156+
self._waiting_closed = True
157+
else:
158+
self._upstream.close()
158159
_log.info('[%s] Downstream connection closed', self.id.hex())
159160

160161
def proxy_data(self, data: memoryview) -> None:
@@ -167,39 +168,34 @@ def proxy_data(self, data: memoryview) -> None:
167168

168169
class UpstreamProtocol(_Base):
169170

171+
__slots__ = ['pp', 'downstream']
172+
170173
def __init__(self, downstream: DownstreamProtocol, buf_len: int,
171-
pp: ProxyProtocol, dnsbl_task: Task[Optional[str]]) -> None:
174+
pp: ProxyProtocol) -> None:
172175
super().__init__(buf_len)
173176
self.pp: Final = pp
174177
self.downstream: Final = downstream
175-
self.dnsbl_task: Final = dnsbl_task
176178

177179
def close(self) -> None:
178180
super().close()
179181
self.downstream.close()
180182

181-
def build_pp_header(self) -> bytes:
183+
def build_pp_header(self, dnsbl: Optional[str]) -> bytes:
182184
sock_info = self.downstream.sock_info
183185
sock = sock_info.socket
184186
ssl_object = sock_info.transport.get_extra_info('ssl_object')
185187
try:
186188
protocol: Optional[SocketKind] = SocketKind(sock.proto)
187189
except ValueError:
188190
protocol = None
189-
dnsbl = self.dnsbl_task.result()
190-
return self.pp.build(sock.getpeername(), sock.getsockname(),
191+
return self.pp.build(sock_info.peername, sock_info.sockname,
191192
family=AddressFamily(sock.family),
192193
protocol=protocol, unique_id=self.downstream.id,
193194
ssl=ssl_object, dnsbl=dnsbl)
194195

195-
def connection_made(self, transport: BaseTransport) -> None:
196-
super().connection_made(transport)
197-
self.dnsbl_task.add_done_callback(self._write_header)
198-
199-
def _write_header(self, task: Task[Any]) -> None:
200-
if self.downstream.is_open:
201-
header = self.build_pp_header()
202-
self.write(memoryview(header))
196+
def write_header(self, dnsbl: Optional[str]) -> None:
197+
header = self.build_pp_header(dnsbl)
198+
self.write(memoryview(header))
203199

204200
def proxy_data(self, data: memoryview) -> None:
205201
self.downstream.write(data)

0 commit comments

Comments
 (0)