mirror of
https://github.com/saltstack/salt.git
synced 2025-04-17 10:10:20 +00:00
fix minion zmq connecting to master configured as IPv6 address
This commit is contained in:
parent
06854cf541
commit
df7338809b
6 changed files with 111 additions and 39 deletions
|
@ -27,6 +27,7 @@ from binascii import crc32
|
|||
# pylint: disable=import-error,no-name-in-module,redefined-builtin
|
||||
from salt.ext import six
|
||||
from salt._compat import ipaddress
|
||||
from salt.utils.network import parse_host_port
|
||||
from salt.ext.six.moves import range
|
||||
from salt.utils.zeromq import zmq, ZMQDefaultLoop, install_zmq, ZMQ_VERSION_INFO
|
||||
|
||||
|
@ -243,27 +244,29 @@ def resolve_dns(opts, fallback=True):
|
|||
|
||||
|
||||
def prep_ip_port(opts):
|
||||
'''
|
||||
parse host:port values from opts['master'] and return valid:
|
||||
master: ip address or hostname as a string
|
||||
master_port: (optional) master returner port as integer
|
||||
|
||||
e.g.:
|
||||
- master: 'localhost:1234' -> {'master': 'localhost', 'master_port': 1234}
|
||||
- master: '127.0.0.1:1234' -> {'master': '127.0.0.1', 'master_port' :1234}
|
||||
- master: '[::1]:1234' -> {'master': '::1', 'master_port': 1234}
|
||||
- master: 'fe80::a00:27ff:fedc:ba98' -> {'master': 'fe80::a00:27ff:fedc:ba98'}
|
||||
'''
|
||||
ret = {}
|
||||
# Use given master IP if "ip_only" is set or if master_ip is an ipv6 address without
|
||||
# a port specified. The is_ipv6 check returns False if brackets are used in the IP
|
||||
# definition such as master: '[::1]:1234'.
|
||||
if opts['master_uri_format'] == 'ip_only' or salt.utils.network.is_ipv6(opts['master']):
|
||||
ret['master'] = opts['master']
|
||||
ret['master'] = str(ipaddress.ip_address(opts['master']))
|
||||
else:
|
||||
ip_port = opts['master'].rsplit(':', 1)
|
||||
if len(ip_port) == 1:
|
||||
# e.g. master: mysaltmaster
|
||||
ret['master'] = ip_port[0]
|
||||
else:
|
||||
# e.g. master: localhost:1234
|
||||
# e.g. master: 127.0.0.1:1234
|
||||
# e.g. master: [::1]:1234
|
||||
# Strip off brackets for ipv6 support
|
||||
ret['master'] = ip_port[0].strip('[]')
|
||||
host, port = parse_host_port(opts['master'])
|
||||
ret = {'master': host}
|
||||
if port:
|
||||
ret.update({'master_port': port})
|
||||
|
||||
# Cast port back to an int! Otherwise a TypeError is thrown
|
||||
# on some of the socket calls elsewhere in the minion and utils code.
|
||||
ret['master_port'] = int(ip_port[1])
|
||||
return ret
|
||||
|
||||
|
||||
|
|
|
@ -33,6 +33,7 @@ import salt.transport.server
|
|||
import salt.transport.mixins.auth
|
||||
from salt.ext import six
|
||||
from salt.exceptions import SaltReqTimeoutError
|
||||
from salt._compat import ipaddress
|
||||
|
||||
from salt.utils.zeromq import zmq, ZMQDefaultLoop, install_zmq, ZMQ_VERSION_INFO, LIBZMQ_VERSION_INFO
|
||||
import zmq.error
|
||||
|
@ -71,33 +72,38 @@ def _get_master_uri(master_ip,
|
|||
'''
|
||||
Return the ZeroMQ URI to connect the Minion to the Master.
|
||||
It supports different source IP / port, given the ZeroMQ syntax:
|
||||
|
||||
// Connecting using a IP address and bind to an IP address
|
||||
rc = zmq_connect(socket, "tcp://192.168.1.17:5555;192.168.1.1:5555"); assert (rc == 0);
|
||||
|
||||
Source: http://api.zeromq.org/4-1:zmq-tcp
|
||||
'''
|
||||
if LIBZMQ_VERSION_INFO >= (4, 1, 6) and ZMQ_VERSION_INFO >= (16, 0, 1):
|
||||
# The source:port syntax for ZeroMQ has been added in libzmq 4.1.6
|
||||
# which is included in the pyzmq wheels starting with 16.0.1.
|
||||
if source_ip or source_port:
|
||||
if source_ip and source_port:
|
||||
return 'tcp://{source_ip}:{source_port};{master_ip}:{master_port}'.format(
|
||||
source_ip=source_ip, source_port=source_port,
|
||||
master_ip=master_ip, master_port=master_port)
|
||||
elif source_ip and not source_port:
|
||||
return 'tcp://{source_ip}:0;{master_ip}:{master_port}'.format(
|
||||
source_ip=source_ip,
|
||||
master_ip=master_ip, master_port=master_port)
|
||||
elif not source_ip and source_port:
|
||||
return 'tcp://0.0.0.0:{source_port};{master_ip}:{master_port}'.format(
|
||||
source_port=source_port,
|
||||
master_ip=master_ip, master_port=master_port)
|
||||
from salt.utils.zeromq import ip_bracket
|
||||
|
||||
master_uri = 'tcp://{master_ip}:{master_port}'.format(
|
||||
master_ip=ip_bracket(master_ip), master_port=master_port)
|
||||
|
||||
if source_ip or source_port:
|
||||
log.warning('Unable to connect to the Master using a specific source IP / port')
|
||||
log.warning('Consider upgrading to pyzmq >= 16.0.1 and libzmq >= 4.1.6')
|
||||
return 'tcp://{master_ip}:{master_port}'.format(
|
||||
master_ip=master_ip, master_port=master_port)
|
||||
if LIBZMQ_VERSION_INFO >= (4, 1, 6) and ZMQ_VERSION_INFO >= (16, 0, 1):
|
||||
# The source:port syntax for ZeroMQ has been added in libzmq 4.1.6
|
||||
# which is included in the pyzmq wheels starting with 16.0.1.
|
||||
if source_ip and source_port:
|
||||
master_uri = 'tcp://{source_ip}:{source_port};{master_ip}:{master_port}'.format(
|
||||
source_ip=ip_bracket(source_ip), source_port=source_port,
|
||||
master_ip=ip_bracket(master_ip), master_port=master_port)
|
||||
elif source_ip and not source_port:
|
||||
master_uri = 'tcp://{source_ip}:0;{master_ip}:{master_port}'.format(
|
||||
source_ip=ip_bracket(source_ip),
|
||||
master_ip=ip_bracket(master_ip), master_port=master_port)
|
||||
elif source_port and not source_ip:
|
||||
ip_any = '0.0.0.0' if ipaddress.ip_address(master_ip).version == 4 else ip_bracket('::')
|
||||
master_uri = 'tcp://{ip_any}:{source_port};{master_ip}:{master_port}'.format(
|
||||
ip_any=ip_any, source_port=source_port,
|
||||
master_ip=ip_bracket(master_ip), master_port=master_port)
|
||||
else:
|
||||
log.warning('Unable to connect to the Master using a specific source IP / port')
|
||||
log.warning('Consider upgrading to pyzmq >= 16.0.1 and libzmq >= 4.1.6')
|
||||
log.warning('Specific source IP / port for connecting to master returner port: configuraion ignored')
|
||||
|
||||
return master_uri
|
||||
|
||||
|
||||
class AsyncZeroMQReqChannel(salt.transport.client.ReqChannel):
|
||||
|
|
|
@ -1922,3 +1922,42 @@ def dns_check(addr, port, safe=False, ipv6=None):
|
|||
raise SaltClientError()
|
||||
raise SaltSystemExit(code=42, msg=err)
|
||||
return resolved
|
||||
|
||||
|
||||
def parse_host_port(host_port):
|
||||
'''
|
||||
Takes a string argument specifying host or host:port.
|
||||
|
||||
Returns a (hostname, port) or (ip_address, port) tuple. If no port is given,
|
||||
the second element of the returned tuple will be None.
|
||||
|
||||
host:port argument, for example, is accepted in the forms of:
|
||||
- hostname
|
||||
- hostname:1234
|
||||
- hostname.domain.tld
|
||||
- hostname.domain.tld:5678
|
||||
- [1234::5]:5678
|
||||
- 1234::5
|
||||
- 10.11.12.13:4567
|
||||
- 10.11.12.13
|
||||
'''
|
||||
_s_ = host_port[:]
|
||||
if _s_[0] == '[':
|
||||
if ']' in host_port[0]:
|
||||
host, _s_ = _s_.lstrip('[]').rsplit(']', 1)
|
||||
if _s_[0] == ':':
|
||||
port = int(_s_.lstrip(':'))
|
||||
else:
|
||||
if len(_s_) > 1:
|
||||
raise ValueError('found ambiguous "{}" port in "{}"'.format(_s_, host_port))
|
||||
host = ipaddress.ipv6IPv6Address(host)
|
||||
else:
|
||||
if _s_.count(':') == 1:
|
||||
host, port = _s_.split(':')
|
||||
port = int(port)
|
||||
else:
|
||||
raise ValueError('too many ":" separators in host:port "{}"'.format(host_port))
|
||||
if ipaddress.is_ip(host):
|
||||
host = ipaddress.ip_address(host)
|
||||
|
||||
return host, port
|
||||
|
|
|
@ -8,6 +8,7 @@ from __future__ import absolute_import, print_function, unicode_literals
|
|||
import logging
|
||||
import tornado.ioloop
|
||||
from salt.exceptions import SaltSystemExit
|
||||
from salt._compat import ipaddress
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
@ -82,6 +83,5 @@ 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 ':' in addr and not addr.startswith('['):
|
||||
return '[{0}]'.format(addr)
|
||||
return addr
|
||||
addr = ipaddress.ip_address(addr)
|
||||
return ('[{}]' if addr.version == 6 else '{}').format(addr)
|
||||
|
|
|
@ -317,11 +317,15 @@ class ZMQConfigTest(TestCase):
|
|||
'''
|
||||
test _get_master_uri method
|
||||
'''
|
||||
|
||||
m_ip = '127.0.0.1'
|
||||
m_port = 4505
|
||||
s_ip = '111.1.0.1'
|
||||
s_port = 4058
|
||||
|
||||
m_ip6 = '1234:5678::9abc'
|
||||
s_ip6 = '1234:5678::1:9abc'
|
||||
|
||||
with patch('salt.transport.zeromq.LIBZMQ_VERSION_INFO', (4, 1, 6)), \
|
||||
patch('salt.transport.zeromq.ZMQ_VERSION_INFO', (16, 0, 1)):
|
||||
# pass in both source_ip and source_port
|
||||
|
@ -330,20 +334,37 @@ class ZMQConfigTest(TestCase):
|
|||
source_ip=s_ip,
|
||||
source_port=s_port) == 'tcp://{0}:{1};{2}:{3}'.format(s_ip, s_port, m_ip, m_port)
|
||||
|
||||
assert salt.transport.zeromq._get_master_uri(master_ip=m_ip6,
|
||||
master_port=m_port,
|
||||
source_ip=s_ip6,
|
||||
source_port=s_port) == 'tcp://[{0}]:{1};[{2}]:{3}'.format(s_ip6, s_port, m_ip6, m_port)
|
||||
|
||||
# source ip and source_port empty
|
||||
assert salt.transport.zeromq._get_master_uri(master_ip=m_ip,
|
||||
master_port=m_port) == 'tcp://{0}:{1}'.format(m_ip, m_port)
|
||||
|
||||
assert salt.transport.zeromq._get_master_uri(master_ip=m_ip6,
|
||||
master_port=m_port) == 'tcp://[{0}]:{1}'.format(m_ip6, m_port)
|
||||
|
||||
# pass in only source_ip
|
||||
assert salt.transport.zeromq._get_master_uri(master_ip=m_ip,
|
||||
master_port=m_port,
|
||||
source_ip=s_ip) == 'tcp://{0}:0;{1}:{2}'.format(s_ip, m_ip, m_port)
|
||||
|
||||
assert salt.transport.zeromq._get_master_uri(master_ip=m_ip6,
|
||||
master_port=m_port,
|
||||
source_ip=s_ip6) == 'tcp://[{0}]:0;[{1}]:{2}'.format(s_ip6, m_ip6, m_port)
|
||||
|
||||
# pass in only source_port
|
||||
assert salt.transport.zeromq._get_master_uri(master_ip=m_ip,
|
||||
master_port=m_port,
|
||||
source_port=s_port) == 'tcp://0.0.0.0:{0};{1}:{2}'.format(s_port, m_ip, m_port)
|
||||
|
||||
# pass in only master_port and ipv6 source_ip and source_port
|
||||
assert salt.transport.zeromq._get_master_uri(master_port=m_port,
|
||||
source_ip=m_port,
|
||||
source_port=s_port) == 'tcp://[::]:{0};[{1}]:{2}'.format(m_port, s_ip6, s_port)
|
||||
|
||||
|
||||
class PubServerChannel(TestCase, AdaptedConfigurationTestCaseMixin):
|
||||
|
||||
|
|
|
@ -50,6 +50,7 @@ class ValidateNetTestCase(TestCase):
|
|||
Test IPv6 address validation
|
||||
'''
|
||||
true_addrs = [
|
||||
'::',
|
||||
'::1',
|
||||
'::1/32',
|
||||
'::1/32',
|
||||
|
@ -62,6 +63,8 @@ class ValidateNetTestCase(TestCase):
|
|||
'::1/0',
|
||||
'::1/32d',
|
||||
'::1/129',
|
||||
'2a03:4000:c:10aa:1017:f00d:aaaa:a:4506',
|
||||
'2a03::1::2',
|
||||
]
|
||||
|
||||
for addr in true_addrs:
|
||||
|
|
Loading…
Add table
Reference in a new issue