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

Handle connection to IPv6 hosts #51

Merged
merged 1 commit into from Apr 20, 2020

Conversation

dangel101
Copy link
Contributor

@dangel101 dangel101 commented Apr 7, 2020

This fix handles adding IPv6 hosts to inventory, which currently fails with the error:
Failed to add host to inventory: SSH error - not found; check DNS or /etc/hosts

@pcuzner @jmolmo please review

@dangel101
Copy link
Contributor Author

@mnecas
@mwperina

try:
s.connect((self.host, self.port))
if ipaddress.ip_address(self.host) == 4:
Copy link
Collaborator

Choose a reason for hiding this comment

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

Hmm, unless I'm missing something this will raise ValueError if you have FQDN specified in host. I think we should use something like this:

ipv6_addr = True
try:
    ipaddress.IPv6Address(self.host)
except ipaddress.AddressValueError:
    ipv6_addr = False

if ipv6_addr:
    s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
    s.connect((self.host, self.port, 0, 0))
else:
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((self.host, self.port))

Copy link
Collaborator

Choose a reason for hiding this comment

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

Marcin is there any better solution how to support all possible combination of host specification (FQDN, IPv4 or IPv6)?

@tinez

Copy link

Choose a reason for hiding this comment

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

I think it's better to start off with socket.getaddrinfo. The way it works, is it returns a list of 5-element tuples that can be used to pass to socket.socket.

  • If you use it with FQDN it will return ipv6 and/or ipv4 addresses in the order that is determined by DNS resolving configuration in your OS i.e.:
>>> pp(socket.getaddrinfo("localhost", 22, 0, socket.SOCK_STREAM))
[(<AddressFamily.AF_INET6: 10>,
  <SocketKind.SOCK_STREAM: 1>,
  6,
  '',
  ('::1', 22, 0, 0)),
 (<AddressFamily.AF_INET: 2>,
  <SocketKind.SOCK_STREAM: 1>,
  6,
  '',
  ('127.0.0.1', 22))]
  • if you use ipv4 it will limit the results to ipv4 addresses only, i.e.:
>>> pp(socket.getaddrinfo("127.0.0.1", 22, 0, socket.SOCK_STREAM))
[(<AddressFamily.AF_INET: 2>,
  <SocketKind.SOCK_STREAM: 1>,
  6,
  '',
  ('127.0.0.1', 22))]
  • analogical scenario for ipv6:
>>> pp(socket.getaddrinfo("::1", 22, 0, socket.SOCK_STREAM))
[(<AddressFamily.AF_INET6: 10>,
  <SocketKind.SOCK_STREAM: 1>,
  6,
  '',
  ('::1', 22, 0, 0))]
  • for non-resolvable hosts it will return the socket.gaierror:
>>> socket.getaddrinfo("imaginary.host.address", 22, 0, socket.SOCK_STREAM)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib64/python3.7/socket.py", line 752, in getaddrinfo
    for res in _socket.getaddrinfo(host, port, family, type, proto, flags):
socket.gaierror: [Errno -2] Name or service not known

@@ -206,9 +207,25 @@ def timeout_handler():
raise SSHTimeout

socket.setdefaulttimeout(self.timeout)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket.getaddrinfo(self.host, self.port, 0, 0, socket.SOL_TCP)
Copy link

@tinez tinez Apr 14, 2020

Choose a reason for hiding this comment

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

I think you didn't understood my previous comment - if you use the return value of the socket.getaddrinfo you can actually get rid of all the if-ology below. Also, it's probably better to use socket.socket as a context manager. Smth like this should do:

try:
    family, _, _, _, addr = socket.getaddrinfo(self.host, self.port, 0, socket.SOCK_STREAM)[0]
except socket.gaierror:
    raise HostNotFound

with socket.socket(family, socket.SOCK_STREAM) as s:
    try:    
        s.connect(addr)
    except ConnectionRefusedError:
        raise SSHNotAccessible
    except socket.timeout:
        raise SSHTimeout

@dangel101 dangel101 force-pushed the B1820988_ipv6_connection_issue branch from 3e66bc8 to 7f51e6f Compare April 16, 2020 11:01
Copy link
Collaborator

@mwperina mwperina left a comment

Choose a reason for hiding this comment

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

Seems OK to me

@jmolmo
Copy link
Collaborator

jmolmo commented Apr 16, 2020

LGTM

@@ -4,6 +4,7 @@
import socket
import sys
import getpass
import ipaddress
Copy link

Choose a reason for hiding this comment

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

Not needed anymore

Copy link
Collaborator

Choose a reason for hiding this comment

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

+1 if we not using it, please remove

raise SSHTimeout
else:
s.shutdown(socket.SHUT_RDWR)
s.close()
Copy link

@tinez tinez Apr 16, 2020

Choose a reason for hiding this comment

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

Since we're using the context manager we don't need call s.close() by ourselves anymore.

Copy link
Collaborator

Choose a reason for hiding this comment

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

+1

try:
s.connect((self.host, self.port))
family, type, proto, canonname, sockaddr = socket.getaddrinfo(self.host, self.port, 0, 0, socket.SOL_TCP)[0]
Copy link

@tinez tinez Apr 16, 2020

Choose a reason for hiding this comment

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

type, proto, canonname, - we don't actually use any of these, so it's better to replace them with _, _, _, - it's a convention.
Also, since we're explicitly asking for a SOCK_STREAM type of socket during creation, it would be nice to pass that to the type argument, so (self.host, self.port, 0, socket.SOCK_STREAM, socket.SOL_TCP) would be better.

Copy link
Collaborator

Choose a reason for hiding this comment

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

+1 again! if we're not using the vars, use _, to prevent flake errors

s.shutdown(socket.SHUT_RDWR)
s.close()

with socket.socket(family, socket.SOCK_STREAM) as s:
Copy link

Choose a reason for hiding this comment

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

We were specifying socket.SOL_TCP during socket.getaddrinfo as protocol, so let's do the same here: (family, socket.SOCK_STREAM, socket.SOL_TCP)

Copy link
Collaborator

Choose a reason for hiding this comment

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

makes sense to me

This fix handles adding IPv6 hosts to inventory, which currently fails with the error:
Failed to add host to inventory: SSH error - <host-ip> not found; check DNS or /etc/hosts
@dangel101 dangel101 force-pushed the B1820988_ipv6_connection_issue branch from 7f51e6f to 46b8787 Compare April 19, 2020 11:03
Copy link
Collaborator

@pcuzner pcuzner left a comment

Choose a reason for hiding this comment

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

lgtm - thanks!

@tinez
Copy link

tinez commented Apr 20, 2020

Looks good, thanks Dana!

@mwperina mwperina merged commit 3569765 into ansible:master Apr 20, 2020
@dominikholler
Copy link

@tinez out of curiosity , why socket.create_connection is not used here?

@tinez
Copy link

tinez commented Apr 30, 2020

@dominikholler simply haven't thought of that... but indeed it seems it would also work and even make the code a bit more simple.

@mwperina
Copy link
Collaborator

Resolves #49

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants