aiohttp
aiohttp copied to clipboard
Need Support pass custom sni(server name ) parameter when make wss request
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
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
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
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.
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())
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.
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())
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
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!
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.
backported to 3.11 in https://github.com/aio-libs/aiohttp/pull/9589
expected in a November release
https://github.com/aio-libs/aiohttp/pull/9589 is now merged and scheduled for the 3.11.0 release