MagicStack / MagicStack/uvloop

Silently closes UDP socket

未關閉
#338 3 則留言 1 個 reaction 已指派 0 人 在 GitHub 檢視

還沒有人認領這個 Issue。

主要語言
Cython
星號
11.9k
分支
616
PR 合併指標
30 天內沒有已合併 PR

描述

  • uvloop version: 0.14
  • Python version: 3.8.2
  • Platform: Linux
  • Can you reproduce the bug with PYTHONASYNCIODEBUG in env?: Yes
  • Does uvloop behave differently from vanilla asyncio? How?: Yes. See below

uvloop silently closes UDP socket when sending data to incorrect destination.

Test program:

import asyncio
import uvloop

# uncomment this line to check it with uvloop
#uvloop.install()


async def main():
    loop = asyncio.get_event_loop()
    transport, proto = await loop.create_datagram_endpoint(
        asyncio.DatagramProtocol,
        local_addr=("192.168.0.2", 0),
    )
    print(transport.get_extra_info('sockname'))
    print("Before sending the message with None destination")
    transport.sendto(b"deadbeef")
    print("After sending the message with None destination")
    
    await asyncio.sleep(0.1)  # let asyncio closes internal socket

    print("Before sending the message with incorrect port destination")
    transport.sendto(b"deadbeef", ("45.83.128.251", 0))
    print("After sending the message with incorrect port destination")
    transport.close()


asyncio.get_event_loop().run_until_complete(main())

asyncio output:

'192.168.0.2', 59471) Before sending the message with None destination Fatal write error on datagram transport protocol: transport: <_SelectorDatagramTransport fd=6 read=polling write=> Traceback (most recent call last): File "/root/.pyenv/versions/3.8.2/lib/python3.8/asyncio/selector_events.py", line 1046, in sendto self._sock.sendto(data, addr) TypeError: sendto(): AF_INET address must be tuple, not NoneType After sending the message with None destination Before sending the message with incorrect port destination Traceback (most recent call last): File "/root/.pyenv/versions/3.8.2/lib/python3.8/asyncio/selector_events.py", line 1046, in sendto self._sock.sendto(data, addr) AttributeError: 'NoneType' object has no attribute 'sendto'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File "", line 1, in
File "/root/.pyenv/versions/3.8.2/lib/python3.8/asyncio/base_events.py", line 616, in run_until_complete
return future.result()
File "", line 14, in main
File "/root/.pyenv/versions/3.8.2/lib/python3.8/asyncio/selector_events.py", line 1056, in sendto
self._fatal_error(
File "/root/.pyenv/versions/3.8.2/lib/python3.8/asyncio/selector_events.py", line 703, in _fatal_error
self._loop.call_exception_handler({
AttributeError: 'NoneType' object has no attribute 'call_exception_handler'

uvloop output:

('192.168.0.2', 33515) Before sending the message with None destination After sending the message with None destination Before sending the message with incorrect port destination After sending the message with incorrect port destination

Then we can cut off first block with sending to None destination. And here we have NEW ONE differece in handling ip:port.

async def main():
    loop = asyncio.get_event_loop()
    transport, proto = await loop.create_datagram_endpoint(
        asyncio.DatagramProtocol,
        local_addr=("192.168.0.2", 0),
    )
    print(transport.get_extra_info('sockname'))
    print("Please use `netstat -au` to see this socket really listening. I will wait 10 sec")
    await asyncio.sleep(10)
    
    print("Before sending the message with incorrect port destination")
    transport.sendto(b"deadbeef", ("45.83.128.251", 0))
    print("After sending the message with incorrect port destination")

    print("Please use `netstat -au` to see this socket STILL really listening. I will wait 10 sec")
    await asyncio.sleep(10)

    transport.close()

When i run it with asyncio event loop all looks fine, socket still present.

When i run it with uvloop it silently closes, and also i can go into infinity wait on recv() call


As i know RFC describe that case as (https://tools.ietf.org/html/rfc8085#section-5.1):

A UDP sender SHOULD NOT use a source port value of zero.  A source
   port number that cannot be easily determined from the address or
   payload type provides protection at the receiver from data injection
   attacks by off-path devices.  A UDP receiver SHOULD NOT bind to port
   zero.

But i got real messages from the internet with that port. I will drop it in my application, but i think uvloop should not silently closes socket. It's very unexpected and differ from asyncio event loop.

Proof from Sentry (https://github.com/spumer/source-query-proxy):

next iteration of recv packet was failed, cause in previous we send response to zero port
изображение

貢獻指南

這個儲存庫沒有索引到貢獻指南

從這裡開始

  1. 先讀完整個 Issue,再讀專案的貢獻指南。
  2. 在 Issue 下留言說明你要接手 —— 這能避免兩個人做同樣的事。
  3. Fork 儲存庫,在一個分支上完成修改。
  4. 送出 Pull Request,並在描述裡引用這個 Issue 編號。

研究方向

首先,使用 uvloop 和 vanilla asyncio 執行 issue 的 UDP 重現程式,重點關注目標連接埠為 0 時的 create_datagram_endpoint 和 transport.sendto。比較傳送後的 socket 狀態以及後續的 recv 行為。完成的標準是:無效的 UDP 目的地不會靜默關閉 transport,且該行為由回歸測試記錄。

由索引模型根據 Issue 內容生成。

評估

技術堆疊
python
領域
networking
Issue 類型
缺陷
難度
4/5
預估耗時
3-5 天
活躍度
停滯
描述清晰度
基本清楚
新手友好度
35/100

把新 issue 寄到你的電子郵件信箱

精選適合新手參與的 GitHub issue 摘要。