bpo-35934: Add socket.create_server() utility function (GH-11784) · python/cpython@eb7e29f
@@ -60,8 +60,8 @@
6060EAGAIN = getattr(errno, 'EAGAIN', 11)
6161EWOULDBLOCK = getattr(errno, 'EWOULDBLOCK', 11)
626263-__all__ = ["fromfd", "getfqdn", "create_connection",
64-"AddressFamily", "SocketKind"]
63+__all__ = ["fromfd", "getfqdn", "create_connection", "create_server",
64+ "has_dualstack_ipv6", "AddressFamily", "SocketKind"]
6565__all__.extend(os._get_exports_list(_socket))
66666767# Set up the socket.AF_* socket.SOCK_* constants as members of IntEnums for
@@ -728,6 +728,89 @@ def create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,
728728else:
729729raise error("getaddrinfo returns an empty list")
730730731+732+def has_dualstack_ipv6():
733+"""Return True if the platform supports creating a SOCK_STREAM socket
734+ which can handle both AF_INET and AF_INET6 (IPv4 / IPv6) connections.
735+ """
736+if not has_ipv6 \
737+or not hasattr(_socket, 'IPPROTO_IPV6') \
738+or not hasattr(_socket, 'IPV6_V6ONLY'):
739+return False
740+try:
741+with socket(AF_INET6, SOCK_STREAM) as sock:
742+sock.setsockopt(IPPROTO_IPV6, IPV6_V6ONLY, 0)
743+return True
744+except error:
745+return False
746+747+748+def create_server(address, *, family=AF_INET, backlog=0, reuse_port=False,
749+dualstack_ipv6=False):
750+"""Convenience function which creates a SOCK_STREAM type socket
751+ bound to *address* (a 2-tuple (host, port)) and return the socket
752+ object.
753+754+ *family* should be either AF_INET or AF_INET6.
755+ *backlog* is the queue size passed to socket.listen().
756+ *reuse_port* dictates whether to use the SO_REUSEPORT socket option.
757+ *dualstack_ipv6*: if true and the platform supports it, it will
758+ create an AF_INET6 socket able to accept both IPv4 or IPv6
759+ connections. When false it will explicitly disable this option on
760+ platforms that enable it by default (e.g. Linux).
761+762+ >>> with create_server((None, 8000)) as server:
763+ ... while True:
764+ ... conn, addr = server.accept()
765+ ... # handle new connection
766+ """
767+if reuse_port and not hasattr(_socket, "SO_REUSEPORT"):
768+raise ValueError("SO_REUSEPORT not supported on this platform")
769+if dualstack_ipv6:
770+if not has_dualstack_ipv6():
771+raise ValueError("dualstack_ipv6 not supported on this platform")
772+if family != AF_INET6:
773+raise ValueError("dualstack_ipv6 requires AF_INET6 family")
774+sock = socket(family, SOCK_STREAM)
775+try:
776+# Note about Windows. We don't set SO_REUSEADDR because:
777+# 1) It's unnecessary: bind() will succeed even in case of a
778+# previous closed socket on the same address and still in
779+# TIME_WAIT state.
780+# 2) If set, another socket is free to bind() on the same
781+# address, effectively preventing this one from accepting
782+# connections. Also, it may set the process in a state where
783+# it'll no longer respond to any signals or graceful kills.
784+# See: msdn2.microsoft.com/en-us/library/ms740621(VS.85).aspx
785+if os.name not in ('nt', 'cygwin') and \
786+hasattr(_socket, 'SO_REUSEADDR'):
787+try:
788+sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
789+except error:
790+# Fail later on bind(), for platforms which may not
791+# support this option.
792+pass
793+if reuse_port:
794+sock.setsockopt(SOL_SOCKET, SO_REUSEPORT, 1)
795+if has_ipv6 and family == AF_INET6:
796+if dualstack_ipv6:
797+sock.setsockopt(IPPROTO_IPV6, IPV6_V6ONLY, 0)
798+elif hasattr(_socket, "IPV6_V6ONLY") and \
799+hasattr(_socket, "IPPROTO_IPV6"):
800+sock.setsockopt(IPPROTO_IPV6, IPV6_V6ONLY, 1)
801+try:
802+sock.bind(address)
803+except error as err:
804+msg = '%s (while attempting to bind on address %r)' % \
805+ (err.strerror, address)
806+raise error(err.errno, msg) from None
807+sock.listen(backlog)
808+return sock
809+except error:
810+sock.close()
811+raise
812+813+731814def getaddrinfo(host, port, family=0, type=0, proto=0, flags=0):
732815"""Resolve host and port into list of address info entries.
733816