Make the IPA installer IPv6 friendly

Notable changes include:
 * parse AAAA records in dnsclient
 * also ask for AAAA records when verifying FQDN
 * do not use functions that are not IPv6 aware - notably socket.gethostbyname()
   The complete list of functions was taken from http://www.akkadia.org/drepper/userapi-ipv6.html
   section "Interface Checklist"
This commit is contained in:
Jakub Hrozek
2010-12-01 17:22:56 +01:00
committed by Rob Crittenden
parent b328d845de
commit ee4d2739f1
6 changed files with 125 additions and 70 deletions

View File

@@ -62,16 +62,19 @@ def parse_options():
def resolve_host(host_name):
ip = None
try:
ip = socket.gethostbyname(host_name)
if ip == "127.0.0.1" or ip == "::1":
print "The hostname resolves to the localhost address (127.0.0.1/::1)"
print "Please change your /etc/hosts file so that the hostname"
print "resolves to the ip address of your network interface."
print ""
print "Please fix your /etc/hosts file and restart the setup program"
return None
addrinfos = socket.getaddrinfo(host_name, None,
socket.AF_UNSPEC, socket.SOCK_DGRAM)
for ai in addrinfos:
ip = ai[4][0]
if ip == "127.0.0.1" or ip == "::1":
print "The hostname resolves to the localhost address (127.0.0.1/::1)"
print "Please change your /etc/hosts file so that the hostname"
print "resolves to the ip address of your network interface."
print ""
print "Please fix your /etc/hosts file and restart the setup program"
return None
ip = addrinfos[0][4][0]
except:
print "Unable to lookup the IP address of the provided host"
return ip

View File

@@ -127,12 +127,17 @@ def get_host_name(no_host_dns):
return hostname
def resolve_host(host_name):
ip = socket.gethostbyname(host_name)
try:
addrinfos = socket.getaddrinfo(host_name, None,
socket.AF_UNSPEC, socket.SOCK_STREAM)
for ai in addrinfos:
ip = ai[4][0]
if ip == "127.0.0.1" or ip == "::1":
raise HostnameLocalhost
if ip == "127.0.0.1" or ip == "::1":
raise HostnameLocalhost
return ip
return addrinfos[0][4][0]
except:
return None
def set_owner(config, dir):
pw = pwd.getpwnam(config.ds_user)
@@ -240,6 +245,8 @@ def install_bind(config, options):
forwarders = ()
bind = bindinstance.BindInstance(dm_password=config.dirman_password)
ip_address = resolve_host(config.host_name)
if not ip_address:
sys.exit("Unable to resolve IP address for host name")
create_reverse = bindinstance.create_reverse(options.unattended)
bind.setup(config.host_name, ip_address, config.realm_name,
config.domain_name, forwarders, options.conf_ntp, create_reverse)

View File

@@ -279,19 +279,22 @@ def read_host_name(host_default,no_host_dns=False):
return host_name
def resolve_host(host_name):
ip = ""
ip = None
try:
ip = socket.gethostbyname(host_name)
if ip == "127.0.0.1" or ip == "::1":
print "The hostname resolves to the localhost address (127.0.0.1/::1)"
print "Please change your /etc/hosts file so that the hostname"
print "resolves to the ip address of your network interface."
print "The KDC service does not listen on localhost"
print ""
print "Please fix your /etc/hosts file and restart the setup program"
return None
addrinfos = socket.getaddrinfo(host_name, None,
socket.AF_UNSPEC, socket.SOCK_DGRAM)
for ai in addrinfos:
ip = ai[4][0]
if ip == "127.0.0.1" or ip == "::1":
print "The hostname resolves to the localhost address (127.0.0.1/::1)"
print "Please change your /etc/hosts file so that the hostname"
print "resolves to the ip address of your network interface."
print "The KDC service does not listen on localhost"
print ""
print "Please fix your /etc/hosts file and restart the setup program"
return None
ip = addrinfos[0][4][0]
except:
print "Unable to lookup the IP address of the provided host"
return ip
@@ -549,7 +552,7 @@ def main():
sys.exit("Aborting installation")
# check the hostname is correctly configured, it must be as the kldap
# utilities just use the hostname as returned by gethostbyname to set
# utilities just use the hostname as returned by getaddrinfo to set
# up some of the standard entries
host_default = ""

View File

@@ -37,6 +37,7 @@ DNS_T_PTR = 12
DNS_T_HINFO = 13
DNS_T_MX = 15
DNS_T_TXT = 16
DNS_T_AAAA = 28
DNS_T_SRV = 33
DNS_T_ANY = 255
@@ -127,6 +128,10 @@ class DNSRData:
# u_int32_t address;
#} dns_rr_a_t;
#
#typedef struct dns_rr_aaaa {
# unsigned char address[16];
#} dns_rr_aaaa_t;
#
#typedef struct dns_rr_cname {
# const char *cname;
#} dns_rr_cname_t;
@@ -239,6 +244,18 @@ def dnsParseA(data, base):
print "A = %d.%d.%d.%d." % (ord(data[0]), ord(data[1]), ord(data[2]), ord(data[3]))
return rdata
def dnsParseAAAA(data, base):
rdata = DNSRData()
if len(data) < 16:
rdata.address = 0
return None
rdata.address = list(struct.unpack('!16B', data))
if DEBUG_DNSCLIENT:
print socket.inet_ntop(socket.AF_INET6,
struct.pack('!16B', *rdata.address))
return rdata
def dnsParseText(data):
if len(data) < 1:
return ("", None)
@@ -413,7 +430,7 @@ def dnsParseResults(results):
DNS_T_NULL: dnsParseNULL, DNS_T_WKS: dnsParseWKS,
DNS_T_PTR: dnsParsePTR, DNS_T_HINFO: dnsParseHINFO,
DNS_T_MX: dnsParseMX, DNS_T_TXT: dnsParseTXT,
DNS_T_SRV: dnsParseSRV}
DNS_T_AAAA : dnsParseAAAA, DNS_T_SRV: dnsParseSRV}
if not rr.dns_type in fmap:
if DEBUG_DNSCLIENT:

View File

@@ -27,6 +27,7 @@ import fileinput
import sys
import struct
import fcntl
import netaddr
from ipapython import ipautil
from ipapython import dnsclient
@@ -42,8 +43,65 @@ def get_fqdn():
fqdn = ""
return fqdn
def verify_fqdn(host_name,no_host_dns=False):
def verify_dns_records(host_name, responses, resaddr, family):
familykw = { 'ipv4' : {
'dns_type' : dnsclient.DNS_T_A,
'socket_family' : socket.AF_INET,
},
'ipv6' : {
'dns_type' : dnsclient.DNS_T_AAAA,
'socket_family' : socket.AF_INET6,
},
}
family = family.lower()
if family not in familykw.keys():
raise RuntimeError("Unknown faimily %s\n" % family)
if len(responses) == 0:
raise IOError(errno.ENOENT,
"Warning: Hostname (%s) not found with NSS calls" % host_name)
rec = None
for rsn in responses:
if rsn.dns_type == familykw[family]['dns_type']:
rec = rsn
break
if rec == None:
raise IOError(errno.ENOENT,
"Warning: Hostname (%s) not found in DNS" % host_name)
if family == 'ipv4':
familykw[family]['address'] = socket.inet_ntop(socket.AF_INET,
struct.pack('!L',rec.rdata.address))
else:
familykw[family]['address'] = socket.inet_ntop(socket.AF_INET6,
struct.pack('!16B', *rec.rdata.address))
# Check that DNS address is the same is address returned via standard glibc calls
dns_addr = netaddr.IPAddress(familykw[family]['address'])
if dns_addr.format() != resaddr:
raise RuntimeError("The network address %s does not match the DNS lookup %s. Check /etc/hosts and ensure that %s is the IP address for %s" % (dns_addr.format(), resaddr, dns_addr.format(), host_name))
rs = dnsclient.query(dns_addr.reverse_dns, dnsclient.DNS_C_IN, dnsclient.DNS_T_PTR)
if len(rs) == 0:
raise RuntimeError("Cannot find Reverse Address for %s (%s)" % (host_name, addr))
rev = None
for rsn in rs:
if rsn.dns_type == dnsclient.DNS_T_PTR:
rev = rsn
break
if rev == None:
raise RuntimeError("Cannot find Reverse Address for %s (%s)" % (host_name, addr))
if rec.dns_name != rev.rdata.ptrdname:
raise RuntimeError("The DNS forward record %s does not match the reverse address %s" % (rec.dns_name, rev.rdata.ptrdname))
def verify_fqdn(host_name,no_host_dns=False):
if len(host_name.split(".")) < 2 or host_name == "localhost.localdomain":
raise RuntimeError("Invalid hostname: " + host_name)
@@ -59,7 +117,7 @@ def verify_fqdn(host_name,no_host_dns=False):
if a[4][0] == '127.0.0.1' or a[4][0] == '::1':
raise RuntimeError("The IPA Server hostname cannot resolve to localhost (%s). A routable IP address must be used. Check /etc/hosts to see if %s is an alias for %s" % (a[4][0], host_name, a[4][0]))
try:
revaddr = a[4][0]
resaddr = a[4][0]
revname = socket.gethostbyaddr(a[4][0])[0]
except:
raise RuntimeError("Unable to resolve the reverse ip address, check /etc/hosts or DNS name resolution")
@@ -77,48 +135,15 @@ def verify_fqdn(host_name,no_host_dns=False):
if rsn.dns_type == dnsclient.DNS_T_CNAME:
raise RuntimeError("The IPA Server Hostname cannot be a CNAME, only A names are allowed.")
# Verify that it is a DNS A record
# Verify that it is a DNS A or AAAA record
rs = dnsclient.query(host_name+".", dnsclient.DNS_C_IN, dnsclient.DNS_T_A)
if len(rs) == 0:
print "Warning: Hostname (%s) not found in DNS" % host_name
return
try:
verify_dns_records(host_name, rs, resaddr, 'ipv4')
except IOError, e:
if e.errno == errno.ENOENT: # retry IPv6
rs = dnsclient.query(host_name+".", dnsclient.DNS_C_IN, dnsclient.DNS_T_AAAA)
verify_dns_records(host_name, rs, resaddr, 'ipv6')
rec = None
for rsn in rs:
if rsn.dns_type == dnsclient.DNS_T_A:
rec = rsn
break
if rec == None:
print "Warning: Hostname (%s) not found in DNS" % host_name
return
# Compare the forward and reverse
forward = rec.dns_name
addr = socket.inet_ntoa(struct.pack('<L',rec.rdata.address))
ipaddr = socket.inet_ntoa(struct.pack('!L',rec.rdata.address))
if revaddr != ipaddr:
raise RuntimeError("The network address %s does not match the reverse lookup %s. Check /etc/hosts and ensure that %s is the IP address for %s" % (ipaddr, revaddr, ipaddr, host_name))
addr = addr + ".in-addr.arpa."
rs = dnsclient.query(addr, dnsclient.DNS_C_IN, dnsclient.DNS_T_PTR)
if len(rs) == 0:
raise RuntimeError("Cannot find Reverse Address for %s (%s)" % (host_name, addr))
rev = None
for rsn in rs:
if rsn.dns_type == dnsclient.DNS_T_PTR:
rev = rsn
break
if rev == None:
raise RuntimeError("Cannot find Reverse Address for %s (%s)" % (host_name, addr))
reverse = rev.rdata.ptrdname
if forward != reverse:
raise RuntimeError("The DNS forward record %s does not match the reverse address %s" % (forward, reverse))
def verify_ip_address(ip):
is_ok = True

View File

@@ -135,7 +135,7 @@ class KrbInstance(service.Service):
self.fqdn = host_name
self.realm = realm_name.upper()
self.host = host_name.split(".")[0]
self.ip = socket.gethostbyname(host_name)
self.ip = socket.getaddrinfo(host_name, None, socket.AF_UNSPEC, socket.SOCK_STREAM)[0][4][0]
self.domain = domain_name
self.suffix = util.realm_to_suffix(self.realm)
self.kdc_password = ipautil.ipa_generate_password()