Merge pull request #4232 from tohojo/ipv6-support

IPv6 support for master-minion communication
This commit is contained in:
Thomas S Hatch 2013-04-28 18:34:28 -07:00
commit ab5f540430
7 changed files with 76 additions and 25 deletions

View File

@ -19,7 +19,7 @@ from salt.version import __version__ # pylint: disable-msg=W402
from salt.utils import migrations
try:
from salt.utils import parsers
from salt.utils import parsers, ip_bracket
from salt.utils.verify import check_user, verify_env, verify_socket
from salt.utils.verify import verify_files
except ImportError as e:
@ -82,6 +82,7 @@ class Master(parsers.MasterOptionParser):
self.config['publish_port'],
self.config['ret_port']):
self.exit(4, 'The ports are not available to bind\n')
self.config['interface'] = ip_bracket(self.config['interface'])
migrations.migrate_paths(self.config)
# Late import so logging works correctly

View File

@ -371,6 +371,9 @@ class Publisher(multiprocessing.Process):
except AttributeError:
pub_sock.setsockopt(zmq.SNDHWM, 1)
pub_sock.setsockopt(zmq.RCVHWM, 1)
if hasattr(zmq, 'IPV4ONLY'):
# IPv6 sockets work for both IPv6 and IPv4 addresses
pub_sock.setsockopt(zmq.IPV4ONLY, 0)
pub_uri = 'tcp://{interface}:{publish_port}'.format(**self.opts)
# Prepare minion pull socket
pull_sock = context.socket(zmq.PULL)
@ -423,6 +426,9 @@ class ReqServer(object):
# Prepare the zeromq sockets
self.uri = 'tcp://{interface}:{ret_port}'.format(**self.opts)
self.clients = self.context.socket(zmq.ROUTER)
if hasattr(zmq, 'IPV4ONLY'):
# IPv6 sockets work for both IPv6 and IPv4 addresses
self.clients.setsockopt(zmq.IPV4ONLY, 0)
self.workers = self.context.socket(zmq.DEALER)
self.w_uri = 'ipc://{0}'.format(
os.path.join(self.opts['sock_dir'], 'workers.ipc')

View File

@ -764,6 +764,9 @@ class Minion(object):
self.socket = self.context.socket(zmq.SUB)
self.socket.setsockopt(zmq.SUBSCRIBE, '')
self.socket.setsockopt(zmq.IDENTITY, self.opts['id'])
if hasattr(zmq, 'IPV4ONLY'):
# IPv6 sockets work for both IPv6 and IPv4 addresses
self.socket.setsockopt(zmq.IPV4ONLY, 0)
if hasattr(zmq, 'RECONNECT_IVL_MAX'):
self.socket.setsockopt(
zmq.RECONNECT_IVL_MAX, self.opts['recon_max']

View File

@ -129,6 +129,9 @@ class SREQ(object):
self.socket.setsockopt(
zmq.RECONNECT_IVL_MAX, 5000
)
if hasattr(zmq, 'IPV4ONLY'):
# IPv6 sockets work for both IPv6 and IPv4 addresses
self.socket.setsockopt(zmq.IPV4ONLY, 0)
self.socket.linger = linger
if id_:
self.socket.setsockopt(zmq.IDENTITY, id_)

View File

@ -304,34 +304,49 @@ def gen_mac(prefix='52:54:'):
mac += random.choice(src) + random.choice(src) + ':'
return mac[:-1]
def ip_bracket(addr):
'''
Convert IP address representation to ZMQ (url) format. ZMQ expects
brackets around IPv6 literals, since they are used in URLs.
'''
if addr and addr.find(":") > -1 and not addr.startswith('['):
return "[{0}]".format(addr)
return addr
def dns_check(addr, safe=False):
'''
Return the ip resolved by dns, but do not exit on failure, only raise an
exception.
exception. Obeys system preference for IPv4/6 address resolution.
'''
error = False
try:
socket.inet_aton(addr)
except socket.error:
# Not a valid ip adder, check DNS
try:
addr = socket.gethostbyname(addr)
except socket.gaierror:
err = ('This master address: \'{0}\' was previously resolvable '
'but now fails to resolve! The previously resolved ip addr '
'will continue to be used').format(addr)
if safe:
import salt.log
if salt.log.is_console_configured():
# If logging is not configured it also means that either
# the master or minion instance calling this hasn't even
# started running
logging.getLogger(__name__).error(err)
raise SaltClientError()
else:
err = err.format(addr)
sys.stderr.write(err)
sys.exit(42)
hostnames = socket.getaddrinfo(addr, None, socket.AF_UNSPEC,
socket.SOCK_STREAM)
if not hostnames:
error = True
else:
h = hostnames[0]
addr = ip_bracket(h[4][0])
except socket.gaierror:
error = True
if error:
err = ('This master address: \'{0}\' was previously resolvable '
'but now fails to resolve! The previously resolved ip addr '
'will continue to be used').format(addr)
if safe:
import salt.log
if salt.log.is_console_configured():
# If logging is not configured it also means that either
# the master or minion instance calling this hasn't even
# started running
logging.getLogger(__name__).error(err)
raise SaltClientError()
else:
err = err.format(addr)
sys.stderr.write(err)
sys.exit(42)
return addr

View File

@ -83,13 +83,31 @@ def zmq_version():
sys.stderr.write('CRITICAL {0}\n'.format(msg))
return False
def lookup_family(hostname):
'''
Lookup a hostname and determine its address family. The first address returned
will be AF_INET6 if the system is IPv6-enabled, and AF_INET otherwise.
'''
# If lookups fail, fall back to AF_INET sockets (and v4 addresses).
fallback = socket.AF_INET
try:
hostnames = socket.getaddrinfo(hostname, None, socket.AF_UNSPEC,
socket.SOCK_STREAM)
if not hostnames:
return fallback
h = hostnames[0]
return h[0]
except socket.gaierror:
return fallback
def verify_socket(interface, pub_port, ret_port):
'''
Attempt to bind to the sockets to verify that they are available
'''
pubsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
retsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
addr_family = lookup_family(interface)
pubsock = socket.socket(addr_family, socket.SOCK_STREAM)
retsock = socket.socket(addr_family, socket.SOCK_STREAM)
try:
pubsock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
pubsock.bind((interface, int(pub_port)))

View File

@ -10,6 +10,7 @@ import stat
import shutil
import resource
import tempfile
import socket
# Import Salt libs
import salt.utils
@ -75,6 +76,10 @@ class TestVerify(TestCase):
def test_verify_socket(self):
self.assertTrue(verify_socket('', 18000, 18001))
if socket.has_ipv6:
# Only run if Python is built with IPv6 support; otherwise
# this will just fail.
self.assertTrue(verify_socket('::', 18000, 18001))
@skipIf(os.environ.get('TRAVIS_PYTHON_VERSION', None) is not None,
'Travis environment does not like too many open files')