Clean IPv6 support
This commit is contained in:
parent
d577661767
commit
9bab3cde5d
13
config
13
config
@ -6,14 +6,11 @@
|
|||||||
# The current values are the default ones
|
# The current values are the default ones
|
||||||
|
|
||||||
[server]
|
[server]
|
||||||
# CalDAV server hostname, empty for all hostnames
|
# CalDAV server hostnames separated by a comma
|
||||||
# if you want to bind to multiple interfaces, seperate them with a comma
|
# IPv4: address:port
|
||||||
# NOTE: IPv6 adresses are configured to only allow IPv6 connections
|
# IPv6: [address]:port
|
||||||
# hence for binding to all IPv4 and IPv6 interfaces:
|
# IPv6 adresses are configured to only allow IPv6 connections
|
||||||
# host = ::, 0.0.0.0
|
hosts = [::]:5232, 0.0.0.0:5232
|
||||||
host =
|
|
||||||
# CalDAV server port
|
|
||||||
port = 5232
|
|
||||||
# Daemon flag
|
# Daemon flag
|
||||||
daemon = False
|
daemon = False
|
||||||
# SSL flag, enable HTTPS protocol
|
# SSL flag, enable HTTPS protocol
|
||||||
|
75
radicale.py
75
radicale.py
@ -38,7 +38,8 @@ arguments.
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import optparse
|
import optparse
|
||||||
import threading, signal
|
import signal
|
||||||
|
import threading
|
||||||
|
|
||||||
import radicale
|
import radicale
|
||||||
|
|
||||||
@ -56,13 +57,9 @@ parser.add_option(
|
|||||||
"-f", "--foreground", action="store_false", dest="daemon",
|
"-f", "--foreground", action="store_false", dest="daemon",
|
||||||
help="launch in foreground (opposite of --daemon)")
|
help="launch in foreground (opposite of --daemon)")
|
||||||
parser.add_option(
|
parser.add_option(
|
||||||
"-H", "--host",
|
"-H", "--hosts",
|
||||||
default=radicale.config.get("server", "host"),
|
default=radicale.config.get("server", "hosts"),
|
||||||
help="set server hostname")
|
help="set server hostnames")
|
||||||
parser.add_option(
|
|
||||||
"-p", "--port", type="int",
|
|
||||||
default=radicale.config.getint("server", "port"),
|
|
||||||
help="set server port")
|
|
||||||
parser.add_option(
|
parser.add_option(
|
||||||
"-s", "--ssl", action="store_true",
|
"-s", "--ssl", action="store_true",
|
||||||
default=radicale.config.getboolean("server", "ssl"),
|
default=radicale.config.getboolean("server", "ssl"),
|
||||||
@ -98,42 +95,36 @@ if options.daemon:
|
|||||||
sys.exit()
|
sys.exit()
|
||||||
sys.stdout = sys.stderr = open(os.devnull, "w")
|
sys.stdout = sys.stderr = open(os.devnull, "w")
|
||||||
|
|
||||||
def exit (servers):
|
# Launch calendar servers
|
||||||
"""Cleanly shutdown all servers.
|
|
||||||
|
|
||||||
Might be called multiple times."""
|
|
||||||
for s in servers:
|
|
||||||
s.shutdown()
|
|
||||||
|
|
||||||
# Launch calendar server
|
|
||||||
server_class = radicale.HTTPSServer if options.ssl else radicale.HTTPServer
|
|
||||||
servers = []
|
servers = []
|
||||||
threads = []
|
server_class = radicale.HTTPSServer if options.ssl else radicale.HTTPServer
|
||||||
|
|
||||||
for host in (x.strip() for x in options.host.split(',')):
|
def exit():
|
||||||
|
"""Cleanly shutdown servers."""
|
||||||
|
while servers:
|
||||||
|
servers.pop().shutdown()
|
||||||
|
|
||||||
|
def serve_forever(server):
|
||||||
|
"""Serve a server forever with no traceback on keyboard interrupts."""
|
||||||
try:
|
try:
|
||||||
server = server_class(
|
server.serve_forever()
|
||||||
(host, options.port), radicale.CalendarHTTPHandler)
|
except KeyboardInterrupt:
|
||||||
servers.append(server)
|
# No unwanted traceback
|
||||||
|
pass
|
||||||
t = threading.Thread(target = server.serve_forever)
|
finally:
|
||||||
threads.append(t)
|
exit()
|
||||||
t.start()
|
|
||||||
except:
|
|
||||||
exit(servers)
|
|
||||||
raise
|
|
||||||
|
|
||||||
# clean exit on SIGTERM
|
# Clean exit on SIGTERM
|
||||||
signal.signal(signal.SIGTERM, lambda *a: exit(servers))
|
signal.signal(signal.SIGTERM, lambda *_: exit())
|
||||||
|
|
||||||
try:
|
for host in options.hosts.split(','):
|
||||||
while threads:
|
address, port = host.strip().rsplit(':', 1)
|
||||||
threads[0].join(1) # try one second
|
address, port = address.strip('[] '), int(port)
|
||||||
if threading.active_count() <= len(threads):
|
servers.append(server_class((address, port), radicale.CalendarHTTPHandler))
|
||||||
# at least one thread died -- exit all
|
|
||||||
break
|
for server in servers[:-1]:
|
||||||
except KeyboardInterrupt:
|
# More servers to come, launch a new thread
|
||||||
# no unwanted traceback :)
|
threading.Thread(target=serve_forever, args=(server,)).start()
|
||||||
pass
|
|
||||||
finally:
|
# Last server, no more thread
|
||||||
exit(servers)
|
serve_forever(servers[-1])
|
||||||
|
@ -85,18 +85,18 @@ class HTTPServer(server.HTTPServer):
|
|||||||
|
|
||||||
# Maybe a Pylint bug, ``__init__`` calls ``server.HTTPServer.__init__``
|
# Maybe a Pylint bug, ``__init__`` calls ``server.HTTPServer.__init__``
|
||||||
# pylint: disable=W0231
|
# pylint: disable=W0231
|
||||||
def __init__(self, address, handler, bind_and_activate = True):
|
def __init__(self, address, handler, bind_and_activate=True):
|
||||||
"""Create server."""
|
"""Create server."""
|
||||||
self.use_ipv6 = ':' in address[0]
|
ipv6 = ":" in address[0]
|
||||||
|
|
||||||
if self.use_ipv6:
|
if ipv6:
|
||||||
self.address_family = socket.AF_INET6
|
self.address_family = socket.AF_INET6
|
||||||
|
|
||||||
# call superclass, but do NOT bind and activate, as we might change socketopts
|
# Do not bind and activate, as we might change socketopts
|
||||||
server.HTTPServer.__init__(self, address, handler, bind_and_activate = False)
|
server.HTTPServer.__init__(self, address, handler, False)
|
||||||
|
|
||||||
if self.use_ipv6:
|
if ipv6:
|
||||||
# only allow IPv6 connections to the IPv6 socket
|
# Only allow IPv6 connections to the IPv6 socket
|
||||||
self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1)
|
self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1)
|
||||||
|
|
||||||
if bind_and_activate:
|
if bind_and_activate:
|
||||||
@ -111,7 +111,7 @@ class HTTPSServer(HTTPServer):
|
|||||||
"""HTTPS server."""
|
"""HTTPS server."""
|
||||||
PROTOCOL = "https"
|
PROTOCOL = "https"
|
||||||
|
|
||||||
def __init__(self, address, handler, bind_and_activate = True):
|
def __init__(self, address, handler, bind_and_activate=True):
|
||||||
"""Create server by wrapping HTTP socket in an SSL socket."""
|
"""Create server by wrapping HTTP socket in an SSL socket."""
|
||||||
# Fails with Python 2.5, import if needed
|
# Fails with Python 2.5, import if needed
|
||||||
# pylint: disable=F0401
|
# pylint: disable=F0401
|
||||||
@ -120,7 +120,7 @@ class HTTPSServer(HTTPServer):
|
|||||||
|
|
||||||
HTTPServer.__init__(self, address, handler, False)
|
HTTPServer.__init__(self, address, handler, False)
|
||||||
self.socket = ssl.wrap_socket(
|
self.socket = ssl.wrap_socket(
|
||||||
self.socket, # we can use this, it is not bound yet
|
self.socket,
|
||||||
server_side=True,
|
server_side=True,
|
||||||
certfile=config.get("server", "certificate"),
|
certfile=config.get("server", "certificate"),
|
||||||
keyfile=config.get("server", "key"),
|
keyfile=config.get("server", "key"),
|
||||||
|
@ -39,8 +39,7 @@ except ImportError:
|
|||||||
# Default configuration
|
# Default configuration
|
||||||
INITIAL_CONFIG = {
|
INITIAL_CONFIG = {
|
||||||
"server": {
|
"server": {
|
||||||
"host": "",
|
"hosts": "[::]:5232, 0.0.0.0:5232",
|
||||||
"port": "5232",
|
|
||||||
"daemon": "False",
|
"daemon": "False",
|
||||||
"ssl": "False",
|
"ssl": "False",
|
||||||
"certificate": "/etc/apache2/ssl/server.crt",
|
"certificate": "/etc/apache2/ssl/server.crt",
|
||||||
|
Loading…
Reference in New Issue
Block a user