Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Autodetect IPv6 connectivity from minion to master #39289

Merged
merged 8 commits into from
Feb 22, 2017
5 changes: 3 additions & 2 deletions doc/ref/configuration/minion.rst
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,10 @@ The option can can also be set to a list of masters, enabling
``ipv6``
--------

Default: ``False``
Default: ``None``
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you explain the reasoning for this please? I like to ensure we have a solid explanation for changing defaults. :]

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reasoning is to enable IPv6 connectivity automatically if user doesn't override the setting. If master address resolves to IPv6 and responds on that address, we take advantage of that immediately.

Just like browsers don't force users to specify that they want google.com over IPv6, we don't force salt users to do the similar thing with salt masters. I think it's much smoother user experience.

It also enables mixed IPv4/IPv6 deployments with multiple masters and that is very useful during migration to IPv6 only stacks.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotcha. I agree entirely that this is a better user experience but I am not very excited about making that change in a minor, bugfix only release. I would accept this PR if we didn't change the default behavior or as it is into the develop branch. Between those two choices, I think that putting this into the develop branch is the better course of action but I'd like to hear your thoughts. Thanks!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It feels here like changing a default value does not change the behavior, but just adds additional behavior: previously by default it tried IPv4 only, but now it tries IPv4 and IPv6.

Also, I bet for most Salt users (at least for me) who did not try IPv6 specifically fact that IPv6 is disabled by default is more a surprise than an expectation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default behavior is all-IPv4 for salt master and salt minion, we don't change that. This patch doesn't change behavior for this case. For people who run IPv6-only deployments already, this patch doesn't change behavior either, because those people already have to set ipv6: true on both minion and master.

The point here is to enable new behavior out of the box, not change anything existing and working.

This patch only changes behavior for the case that was not possible before, so it's hard to break anything existing in the wild. The only thing I can think of is a deployment where salt master works on IPv4, listens and accepts connections on IPv6, but otherwise breaks on IPv6. You have to try very hard to achieve that and I double anyone has would have this issue.

In fact, ipv6 option wasn't even documented, I changed that recently in #39131.

If you still feel very strongly about development branch, I can do that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You both make very strong points. I appreciate the explanation and you taking the time to walk me through your reasoning. We'll get this in.


Whether the master should be connected over IPv6.
Whether the master should be connected over IPv6. By default salt minion
will try to automatically detect IPv6 connectivity to master.

.. code-block:: yaml

Expand Down
2 changes: 1 addition & 1 deletion salt/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -990,7 +990,7 @@ def _gather_buffer_space():
'mine_interval': 60,
'ipc_mode': _DFLT_IPC_MODE,
'ipc_write_buffer': _DFLT_IPC_WBUFFER,
'ipv6': False,
'ipv6': None,
'file_buffer_size': 262144,
'tcp_pub_port': 4510,
'tcp_pull_port': 4511,
Expand Down
10 changes: 5 additions & 5 deletions salt/minion.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@
# 6. Handle publications


def resolve_dns(opts, fallback=True):
def resolve_dns(opts, fallback=True, connect=True):
'''
Resolves the master_ip and master_uri options
'''
Expand All @@ -149,21 +149,21 @@ def resolve_dns(opts, fallback=True):
if opts['master'] == '':
raise SaltSystemExit
ret['master_ip'] = \
salt.utils.dns_check(opts['master'], True, opts['ipv6'])
salt.utils.dns_check(opts['master'], opts['master_port'], True, opts['ipv6'], connect)
except SaltClientError:
if opts['retry_dns']:
while True:
import salt.log
msg = ('Master hostname: \'{0}\' not found. Retrying in {1} '
'seconds').format(opts['master'], opts['retry_dns'])
msg = ('Master hostname: \'{0}\' not found or not responsive. '
'Retrying in {1} seconds').format(opts['master'], opts['retry_dns'])
if salt.log.setup.is_console_configured():
log.error(msg)
else:
print('WARNING: {0}'.format(msg))
time.sleep(opts['retry_dns'])
try:
ret['master_ip'] = salt.utils.dns_check(
opts['master'], True, opts['ipv6']
opts['master'], opts['master_port'], True, opts['ipv6'], connect
)
break
except SaltClientError:
Expand Down
2 changes: 1 addition & 1 deletion salt/transport/zeromq.py
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,7 @@ def __init__(self,
zmq.RECONNECT_IVL_MAX, self.opts['recon_max']
)

if self.opts['ipv6'] is True and hasattr(zmq, 'IPV4ONLY'):
if (self.opts['ipv6'] is True or ':' in self.opts['master_ip']) and hasattr(zmq, 'IPV4ONLY'):
# IPv6 sockets work for both IPv6 and IPv4 addresses
self._socket.setsockopt(zmq.IPV4ONLY, 0)

Expand Down
33 changes: 26 additions & 7 deletions salt/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -716,10 +716,11 @@ def ip_bracket(addr):
return addr


def dns_check(addr, safe=False, ipv6=False):
def dns_check(addr, port, safe=False, ipv6=None, connect=True):
'''
Return the ip resolved by dns, but do not exit on failure, only raise an
exception. Obeys system preference for IPv4/6 address resolution.
Tries to connect to the address before considering it useful.
'''
error = False
try:
Expand All @@ -732,12 +733,30 @@ def dns_check(addr, safe=False, ipv6=False):
if not hostnames:
error = True
else:
addr = False
resolved = False
for h in hostnames:
if h[0] == socket.AF_INET or (h[0] == socket.AF_INET6 and ipv6):
addr = ip_bracket(h[4][0])
if h[0] == socket.AF_INET and ipv6 is True:
continue
if h[0] == socket.AF_INET6 and ipv6 is False:
continue
if h[0] == socket.AF_INET6 and connect is False and ipv6 is None:
continue

candidate_addr = ip_bracket(h[4][0])

if not connect:
resolved = candidate_addr

s = socket.socket(h[0], socket.SOCK_STREAM)
try:
s.connect((candidate_addr.strip('[]'), port))
s.close()

resolved = candidate_addr
break
if not addr:
except socket.error:
pass
if not resolved:
error = True
except TypeError:
err = ('Attempt to resolve address \'{0}\' failed. Invalid or unresolveable address').format(addr)
Expand All @@ -746,7 +765,7 @@ def dns_check(addr, safe=False, ipv6=False):
error = True

if error:
err = ('DNS lookup of \'{0}\' failed.').format(addr)
err = ('DNS lookup or connection check of \'{0}\' failed.').format(addr)
if safe:
if salt.log.is_console_configured():
# If logging is not configured it also means that either
Expand All @@ -755,7 +774,7 @@ def dns_check(addr, safe=False, ipv6=False):
log.error(err)
raise SaltClientError()
raise SaltSystemExit(code=42, msg=err)
return addr
return resolved


def required_module_list(docstring=None):
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/config/config_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,7 @@ def test_syndic_config(self):
syndic_opts = sconfig.syndic_config(
syndic_conf_path, minion_conf_path
)
syndic_opts.update(salt.minion.resolve_dns(syndic_opts))
syndic_opts.update(salt.minion.resolve_dns(syndic_opts, connect=False))
root_dir = syndic_opts['root_dir']
# id & pki dir are shared & so configured on the minion side
self.assertEqual(syndic_opts['id'], 'minion')
Expand Down