[ADD] core: support for binding IPv6 in worker mode

Because Werkzeug supports ipv6 natively (and has since ~2010
pallets/werkzeug@13a76262a4), running
Odoo in threaded mode allows binding to an ipv6 address.

However the prefork server is part of Odoo, and only creates
AF_INET (ipv4) sockets. Thus switching from threaded to worker mode
breaks the bind.

Add a family switch on the `http-interface`, so binding to ipv6
addresses also works in ipv6. Gevent already does that switching
internally, so the evented worker works out of the box, it was only
the prefork / workers which did not.

Looking at both the Werkzeug and Gevent implementations:

- https://github.com/pallets/werkzeug/blob/f9906fa6d83dd668f9214acef458419756bbc062/src/werkzeug/serving.py#L607-L614
- https://github.com/gevent/gevent/blob/1e412d35526183b26c1abf2eb658cbef661f5f70/src/gevent/baseserver.py#L415-L433

Both primarily check whether the host contains `:`. Werkzeug also
checks if `socket.AF_INET6` exists, however gevent doesn't bother with
that. Looking at the
source (https://github.com/python/cpython/blob/7d801f245e2021d19daff105ce722f22aa844391/Modules/socketmodule.c#L7419-L7421),
it is indeed possible for `AF_INET6` to be missing, however that
requires specifically compiling Python in an environment which doesn't
support IPv6.

Meanwhile it's also possible to disable ipv6 at runtime, in which case
I'd assume the `socket.AF_INET6` constant is present, and creating the
socket fails, which nobody guards against. Therefore just ignore the
entire thing, it doesn't seem worth the hassle (or the questioning) to
check whether the constant is present, especially since this is only a
concern when the user specifically requires an IPv6 address.

Fixes #35782

closes odoo/odoo#114666

Signed-off-by: Julien Castiaux (juc) <juc@odoo.com>
This commit is contained in:
Xavier Morel
2023-03-08 13:49:05 +01:00
parent 055cc1a0f0
commit eb5515bee9
+4 -1
View File
@@ -921,7 +921,10 @@ class PreforkServer(CommonServer):
if config['http_enable']:
# listen to socket
_logger.info('HTTP service (werkzeug) running on %s:%s', self.interface, self.port)
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
family = socket.AF_INET
if ':' in self.interface:
family = socket.AF_INET6
self.socket = socket.socket(family, socket.SOCK_STREAM)
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.socket.setblocking(0)
self.socket.bind((self.interface, self.port))