|
2 | 2 |
|
3 | 3 | import base64 |
4 | 4 | import contextlib |
| 5 | +import errno |
5 | 6 | import os |
6 | 7 | import secrets |
7 | 8 | import socket |
@@ -182,42 +183,18 @@ def _parse_frag_header(packet: bytes) -> tuple[str, int, int] | None: |
182 | 183 | return transfer_id, index, total |
183 | 184 |
|
184 | 185 |
|
185 | | -def _kill_stale_udp_holder(port: int) -> bool: |
186 | | - """Find and kill a stale process holding a UDP port (Windows + POSIX).""" |
187 | | - import subprocess |
188 | | - my_pid = os.getpid() |
189 | | - try: |
190 | | - result = subprocess.run( |
191 | | - ["netstat", "-ano", "-p", "UDP"], |
192 | | - capture_output=True, text=True, timeout=5, |
193 | | - ) |
194 | | - for line in (result.stdout or "").splitlines(): |
195 | | - if "UDP" not in line or f":{port}" not in line: |
196 | | - continue |
197 | | - parts = line.split() |
198 | | - if len(parts) < 3: |
199 | | - continue |
200 | | - try: |
201 | | - holder_pid = int(parts[-1]) |
202 | | - except ValueError: |
203 | | - continue |
204 | | - if holder_pid == my_pid or holder_pid == 0: |
205 | | - continue |
206 | | - audit_logger.log( |
207 | | - "killing_stale_port_holder", |
208 | | - target_id=f"pid={holder_pid}", |
209 | | - target_type="transport", |
210 | | - details={"port": port}, |
211 | | - ) |
212 | | - with contextlib.suppress(Exception): |
213 | | - subprocess.run( |
214 | | - ["taskkill", "/F", "/PID", str(holder_pid)], |
215 | | - capture_output=True, timeout=5, |
216 | | - ) |
217 | | - return True |
218 | | - except Exception: |
219 | | - pass |
220 | | - return False |
| 186 | +def _is_address_in_use_error(exc: OSError) -> bool: |
| 187 | + err_no = getattr(exc, "errno", None) |
| 188 | + winerror = getattr(exc, "winerror", None) |
| 189 | + return err_no == errno.EADDRINUSE or err_no == 10048 or winerror == 10048 |
| 190 | + |
| 191 | + |
| 192 | +def _raise_udp_bind_conflict(host: str, port: int, exc: OSError) -> None: |
| 193 | + raise OSError( |
| 194 | + getattr(exc, "errno", None) or getattr(exc, "winerror", None) or errno.EADDRINUSE, |
| 195 | + f"UDP transport cannot bind {host}:{port}: port already in use. " |
| 196 | + "Stop the conflicting process or choose a different port.", |
| 197 | + ) from exc |
221 | 198 |
|
222 | 199 |
|
223 | 200 | def _configure_udp_socket_buffers(sock: socket.socket) -> None: |
@@ -285,20 +262,10 @@ def start(self) -> TransportRuntime: |
285 | 262 | try: |
286 | 263 | sock.bind((self.host, self.port)) |
287 | 264 | except OSError as bind_err: |
288 | | - if getattr(bind_err, "winerror", None) == 10048 or bind_err.errno == 10048: |
289 | | - killed = _kill_stale_udp_holder(self.port) |
290 | | - if killed: |
291 | | - time.sleep(0.5) |
292 | | - try: |
293 | | - sock.bind((self.host, self.port)) |
294 | | - except OSError: |
295 | | - sock.close() |
296 | | - sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) |
297 | | - sock.bind((self.host, self.port)) |
298 | | - else: |
299 | | - raise |
300 | | - else: |
301 | | - raise |
| 265 | + sock.close() |
| 266 | + if _is_address_in_use_error(bind_err): |
| 267 | + _raise_udp_bind_conflict(self.host, self.port, bind_err) |
| 268 | + raise |
302 | 269 |
|
303 | 270 | bound_port = int(sock.getsockname()[1]) |
304 | 271 |
|
|
0 commit comments