aiohttp icon indicating copy to clipboard operation
aiohttp copied to clipboard

Need Support pass custom sni(server name ) parameter when make wss request

Open mebest100 opened this issue 1 year ago • 9 comments

Is your feature request related to a problem?

Need Support pass custom sni(server name ) parameter when make wss request Because When pass CDN with secure websocket , the SNI is needed

Describe the solution you'd like

Need Support pass custom sni(server name ) parameter when make wss request Because When pass CDN with secure websocket , the SNI is needed

Describe alternatives you've considered

No alternative

Related component

Client

Additional context

No response

Code of Conduct

  • [X] I agree to follow the aio-libs Code of Conduct

mebest100 avatar Oct 04 '24 02:10 mebest100

SNI is part of the TLS layer, so I assume you can do this by passing a custom SSLContext. If you figure it out, it might be nice to create a PR adding another example to the docs: https://docs.aiohttp.org/en/stable/client_advanced.html#example-use-certifi

Dreamsorcerer avatar Oct 04 '24 13:10 Dreamsorcerer

SNI is part of the TLS layer, so I assume you can do this by passing a custom SSLContext. If you figure it out, it might be nice to create a PR adding another example to the docs: https://docs.aiohttp.org/en/stable/client_advanced.html#example-use-certifi

I search out ssl material and find out it need wrap a new socket if need pass SNI in TLS layer just like follow codes

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
wrapped_sock    = ssl_context.wrap_socket(sock,server_hostname=sni_hostname)

But it seems there is no place for aiohttp to accept the argument of wrapped_sock when make wss request

mebest100 avatar Oct 05 '24 14:10 mebest100

From what I can tell in the code, a similar socket wrap is already happening, and you can use server_hostname parameter in the request, which will get passed along to that: https://docs.aiohttp.org/en/stable/client_reference.html#aiohttp.ClientSession.request

If that works for you, then it'd be good to update the docs there to mention SNI, so other users will be able to find it more easily in future.

Dreamsorcerer avatar Oct 06 '24 20:10 Dreamsorcerer

It still cannot work when use server_hostname parameter in the request, it will report err _RuntimeError: Event loop is closed

The code as follows:

import aiohttp
import asyncio
import base64
import hashlib

async def connect_via_request():
    url = "wss://${cdn_edgeIP}:443/ws"      
    custom_hostname = "${your-CDN-SNI }" 

    key = base64.b64encode(hashlib.sha1(b"random_key").digest()).decode('utf-8')

    headers = {
        "Host": custom_hostname,
        "Upgrade": "websocket",
        "Connection": "Upgrade",
        "Sec-WebSocket-Key": key,
        "Sec-WebSocket-Version": "13",
    }

    async with aiohttp.ClientSession() as session:
        async with session.request("GET", url,ssl=True,headers=headers,server_hostname=custom_hostname) as response:
            print(f"Response status: {response.status}")
            if response.status == 101:
                print("WebSocket connection established!")
             

asyncio.run(connect_via_request())

mebest100 avatar Oct 07 '24 10:10 mebest100

It still cannot work when use server_hostname parameter in the request, it will report err _RuntimeError: Event loop is closed

That doesn't sound like a related issue, please include the traceback.

I forgot it was websocket, so obviously you'll need ws_connect(), not request(). The parameter is missing in the docs, but appears to be present in the code, so should still work.

Dreamsorcerer avatar Oct 07 '24 11:10 Dreamsorcerer

But if with ws_connect(), it has not argument of server_hostname to pass, and will report error of sslv3 alert handshake failure Full Traceback as follows:

Traceback (most recent call last):
  File "I:\ProgramData\Anaconda3\lib\site-packages\aiohttp\connector.py", line 1068, in _wrap_create_connection
    return await self._loop.create_connection(*args, **kwargs, sock=sock)
  File "I:\ProgramData\Anaconda3\lib\asyncio\base_events.py", line 1050, in create_connection
    transport, protocol = await self._create_connection_transport(
  File "I:\ProgramData\Anaconda3\lib\asyncio\base_events.py", line 1080, in _create_connection_transport
    await waiter
  File "I:\ProgramData\Anaconda3\lib\asyncio\sslproto.py", line 529, in data_received
    ssldata, appdata = self._sslpipe.feed_ssldata(data)
  File "I:\ProgramData\Anaconda3\lib\asyncio\sslproto.py", line 189, in feed_ssldata
    self._sslobj.do_handshake()
  File "I:\ProgramData\Anaconda3\lib\ssl.py", line 944, in do_handshake
    self._sslobj.do_handshake()
ssl.SSLError: [SSL: SSLV3_ALERT_HANDSHAKE_FAILURE] sslv3 alert handshake failure (_ssl.c:1125)

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "d:\PythonCode\websocketTest\TestWebsocket2.py", line 40, in <module>
    asyncio.run(websocket_client())
  File "I:\ProgramData\Anaconda3\lib\asyncio\runners.py", line 44, in run
    return loop.run_until_complete(main)
  File "I:\ProgramData\Anaconda3\lib\asyncio\base_events.py", line 616, in run_until_complete
    return future.result()
  File "d:\PythonCode\websocketTest\TestWebsocket2.py", line 31, in websocket_client
    async with session.ws_connect(url,ssl=ssl_context,headers=headers) as ws:
  File "I:\ProgramData\Anaconda3\lib\site-packages\aiohttp\client.py", line 1355, in __aenter__
    self._resp: _RetType = await self._coro
  File "I:\ProgramData\Anaconda3\lib\site-packages\aiohttp\client.py", line 933, in _ws_connect
    resp = await self.request(
  File "I:\ProgramData\Anaconda3\lib\site-packages\aiohttp\client.py", line 659, in _request
    conn = await self._connector.connect(
  File "I:\ProgramData\Anaconda3\lib\site-packages\aiohttp\connector.py", line 557, in connect
    proto = await self._create_connection(req, traces, timeout)
  File "I:\ProgramData\Anaconda3\lib\site-packages\aiohttp\connector.py", line 1002, in _create_connection
    _, proto = await self._create_direct_connection(req, traces, timeout)
  File "I:\ProgramData\Anaconda3\lib\site-packages\aiohttp\connector.py", line 1336, in _create_direct_connection
    raise last_exc
  File "I:\ProgramData\Anaconda3\lib\site-packages\aiohttp\connector.py", line 1305, in _create_direct_connection
    transp, proto = await self._wrap_create_connection(
  File "I:\ProgramData\Anaconda3\lib\site-packages\aiohttp\connector.py", line 1072, in _wrap_create_connection
    raise ClientConnectorSSLError(req.connection_key, exc) from exc
aiohttp.client_exceptions.ClientConnectorSSLError: Cannot connect to host 104.16.177.217:443 ssl:<ssl.SSLContext object at 0x00000000033026C0> [[SSL: SSLV3_ALERT_HANDSHAKE_FAILURE] sslv3 alert handshake failure (_ssl.c:1125)]

Original code as follows:

import asyncio
import aiohttp
import ssl
import socket

async def websocket_client():    
    url = "wss://${cdn_edgeIP}:443/ws" 
    
    custom_hostname = "{your-CDN-SNI }"  
    
    ssl_context = ssl.create_default_context()
    ssl_context.minimum_version = ssl.TLSVersion.TLSv1_2
    

    headers = {
        'Host': custom_hostname,
        "User-agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36"
    }
    
    

    async with aiohttp.ClientSession(connector=aiohttp.TCPConnector()) as session:
        async with session.ws_connect(url,ssl=ssl_context,headers=headers) as ws:
            await ws.send_str("Hello from WSS client with custom TLS")
            async for msg in ws:
                if msg.type == aiohttp.WSMsgType.TEXT:
                    print(f"Received: {msg.data}")
                elif msg.type == aiohttp.WSMsgType.ERROR:
                    print(f"Error: {ws.exception()}")


asyncio.run(websocket_client())

mebest100 avatar Oct 07 '24 11:10 mebest100

it has not argument of server_hostname to pass

Appears nobody completed the backport. Can you try on master? If you want to get the parameter added for 3.11, then feel free to complete the backport following these instructions: https://github.com/aio-libs/aiohttp/pull/7942#issuecomment-2396685986

Dreamsorcerer avatar Oct 07 '24 11:10 Dreamsorcerer

it has not argument of server_hostname to pass

Appears nobody completed the backport. Can you try on master? If you want to get the parameter added for 3.11, then feel free to complete the backport following these instructions: #7942 (comment)

I'm not sure whether it will work when added parameter of server_hostname within aiohttp, maybe I hv to modify the aiohttp code locally to see if it will work. And from my side, I find if use ssl to wrap sock with sni, it will work ,below code can work

import socket
import ssl
import base64
import hashlib
import os

DialUrl = '${cdn_edgeIP}'  #
custom_sni = '{your-CDN-SNI }'

sock = socket.create_connection((DialUrl, 443))

ssl_context = ssl.create_default_context()
ssl_sock = ssl_context.wrap_socket(sock, server_hostname=custom_sni)

sec_websocket_key = base64.b64encode(os.urandom(16)).decode('utf-8')
request = (
    f"GET /ws HTTP/1.1\r\n"
    f"Host: {custom_sni}\r\n"
    f"Upgrade: websocket\r\n"
    f"Connection: Upgrade\r\n"
    f"Sec-WebSocket-Key: {sec_websocket_key}\r\n"
    f"Sec-WebSocket-Version: 13\r\n\r\n"
)

ssl_sock.sendall(request.encode('utf-8'))

response = ssl_sock.recv(4096)
print(response.decode('utf-8'))


if b'101 Switching Protocols' in response:
    print("WebSocket handshake successful!")
else:
    print("WebSocket handshake failed.")


It can get success reply as follows:

HTTP/1.1 101 Switching Protocols

Date: Mon, 07 Oct 2024 11:54:25 GMT

Connection: upgrade

Upgrade: websocket

Sec-WebSocket-Accept: lrGBILXOR2bPENNlilmCxmz27bs=

CF-Cache-Status: DYNAMIC

Report-To: {"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v4?s=A5qsg9rVFqNfr6Wqhrl2gNgYGr5VNHSMfI0bmg8b9iW6ry4I2gPTbfENgIf9VHTdCzR3VJg28OL0R41%2BGloybsUAtoL4IGDJNUcN%2FZZSPD7uCfVwSWpI%2FcID3qM4N7p9ltXTUofeku8mej76J137"}],"group":"cf-nel","max_age":604800}

NEL: {"success_fraction":0,"report_to":"cf-nel","max_age":604800}

Server: cloudflare

CF-RAY: 8cedb2440921270a-SJC

alt-svc: h3=":443"; ma=86400




WebSocket handshake successful!

mebest100 avatar Oct 07 '24 11:10 mebest100

maybe I hv to modify the aiohttp code locally to see if it will work

You can literally just git clone the repo and use PYTHONPATH=/path/to/aiohttp env var.

Dreamsorcerer avatar Oct 07 '24 12:10 Dreamsorcerer

backported to 3.11 in https://github.com/aio-libs/aiohttp/pull/9589

expected in a November release

bdraco avatar Oct 29 '24 20:10 bdraco

https://github.com/aio-libs/aiohttp/pull/9589 is now merged and scheduled for the 3.11.0 release

bdraco avatar Oct 30 '24 04:10 bdraco