-
-
Notifications
You must be signed in to change notification settings - Fork 30.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Flaky SO_REUSEPORT/SO_REUSEADDR client socket #126539
Comments
First, You need create a connection pool liked mechanism to reuse the connection. Second, the For me, You should not use REUSEPORT/REUSEADDR in client side, connection pool liked mechanism should be better |
@picnixz I think this is not a bug for stdlib, maybe we can remove the label. |
BTW, If you think this should be a problem, You can use the https://github.com/cilium/pwru to trace your timeout packet on your Linux environment. And put the detail here, I can help you with more detail. |
You misunderstood the point. Unfortunately. As it happen, we found the solution, by further adding opts to the socket, indicating the OS to release it sooner. c poc #include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <arpa/inet.h>
void cpython_bug_bind_so_reuseport() {
int sockfd = 0, n = 0;
struct sockaddr_in serv_addr, local_addr;
/* a socket is created through call to socket() function */
if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
printf("\n Error : Could not create socket \n");
}
memset(&serv_addr, '0', sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(443);
serv_addr.sin_addr.s_addr = inet_addr("1.1.1.1");
local_addr.sin_family = AF_INET;
local_addr.sin_addr.s_addr = INADDR_ANY;
local_addr.sin_port = htons(12010);
int enable = 1;
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char*)&enable, sizeof(enable));
local_addr.sin_addr.s_addr = inet_addr("192.168.1.12");
if (bind(sockfd, (struct sockaddr*) &local_addr, sizeof(struct sockaddr_in)) != 0) {
printf("\n Error : bind error");
}
if( connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0)
{
printf("\n Error : Connect Failed \n");
} else {
printf("\n Info : Connect Ok \n");
}
const struct linger opt = { .l_onoff = 1, .l_linger = 0 };
setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &opt, sizeof opt);
shutdown(sockfd, SHUT_WR);
close(sockfd);
}
int main(int argc, char *argv[])
{
cpython_bug_bind_so_reuseport();
cpython_bug_bind_so_reuseport();
return 0;
} now it work reliably in sync mode, async part still doesn't work, but I suppose the opts aren't applied correctly either. |
import asyncio
import socket
import struct
async def cpython_bug_bind_so_reuseport():
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
sock.bind(("0.0.0.0", 5784))
loop = asyncio.get_event_loop()
await loop.sock_connect(sock, ("1.1.1.1", 443))
sock.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER,
struct.pack('ii', 1, 0))
sock.shutdown(socket.SHUT_RDWR)
sock.close()
async def run():
await cpython_bug_bind_so_reuseport()
await cpython_bug_bind_so_reuseport()
if __name__ == "__main__":
asyncio.run(run()) This code works fine on my environment. I'm not sure this is async code in your description or not. |
As I said,
So, yes it is working. |
Bug report
Bug description:
Unable to reliably be able to reuse a outgoing port using a client socket.
Depending on the interpreter version and/or OS, it sometime fails, sometime succeed.
You should get intermittent
[Errno 99] Cannot assign requested address
or similar depending on the OS.I ran the following in:
Across Python 3.7 -- 3.13
Here are the results:
Linux 3.7 (sync OK, async OK)
Windows 3.7 (sync OK, async OK)
Linux 3.11 (sync OK, async KO)
Windows 3.11 (sync OK, async KO)
Windows 3.10 (sync KO, async KO)
MacOS 3.8+ (sync KO, async KO)
Curiously, if you ran:
By running the interpreter twice (exec
python sample.py
twice or more), it will work as much as needed. Something happen at interpreter shutdown that should happen before?Low level speaking,
socket.SO_REUSEPORT
is applied when available, otherwise usingsocket.SO_REUSEADDR
instead.The
sock.bind((addr, port))
is applied after setting sock opts and before connecting to remote peer.As it seems to work flawlessly on Python 3.7, I expected it to work on later versions also.
See the minimal code to reproduce this (sync only):
I posted the "higher" level code because, sometime the execution does not raise "Cannot assign requested address" but timeout instead. So the bind and connect pass but the socket is unusable. You will have to insist a bit to get this behavior.
Did I miss something? The official docs does not clearly mention
.bind(..)
usage with client-side socket, so we're in a grey area.Regards,
CPython versions tested on:
3.9, 3.10, 3.11, 3.12, 3.13
Operating systems tested on:
Linux, macOS, Windows
The text was updated successfully, but these errors were encountered: