diff --git a/src/hostinfo/dhcpd.py b/src/hostinfo/dhcpd.py index 65d74b6ba769e8bb66a4b9e958e42d8241276d42..e1f69efd718669b31589269b8c82453d18e50568 100755 --- a/src/hostinfo/dhcpd.py +++ b/src/hostinfo/dhcpd.py @@ -1,7 +1,22 @@ import sys import os from hostinfo.util import ntoa, aton +import hostinfo.util as util +CLASS_PXECLIENT = """ + class "pxeclient" { + match if substring (option vendor-class-identifier, 0, 9) = "PXEClient"; + next-server %(next_server)s; + if option arch = 00:06 { + filename "pxelinux/bootia32.efi"; + } else if option arch = 00:07 { + filename "pxelinux/bootx64.efi"; + } else { + filename "pxelinux.0"; + } + } + } +""" def MacOS_NETBOOT(dhcphost): if not os.path.exists('/local/macos'): return '' @@ -86,59 +101,58 @@ def generate(tree, dhcphost, kickstart, next_server=None): address.append(ip.address[0]) pass pass + result += 'shared-network "MAC(%s)" {\n\n' % i.ethernet[0] result += emit_network(tree, dhcphost, address, kickstart, - i.ethernet[0], next_server=next_server) + interface=i, next_server=next_server) + result += "}\n" for v in vlan: result += emit_network(tree, dhcphost, [ v ], kickstart, - None, next_server=next_server) + next_server=next_server) pass pass return result -def emit_network(tree, dhcphost, addr, kickstart, ethernet=None, - next_server=None): +def emit_network(tree, dhcphost, addr, kickstart, + interface=None, next_server=None): result = "" - network = {} - for n in tree._subnet_: - if not n.network[0]: - continue - netmask = aton(n.netmask[0]) - subnet = aton(n.network[0]) & netmask + subnet = {} + for s in filter(util.network, tree._subnet_): + n = util.network(s) for a in addr: - if aton(a) & netmask == subnet: - network[ntoa(subnet)] = n - if len(network.keys()) > 1: - # Multiple networks served on this interface - result += 'shared-network "MAC(%s)" {\n\n' % ethernet - for n in network.values(): - for l in emit_subnet(tree, n, dhcphost, kickstart, - next_server=next_server).split("\n"): - result += " %s\n" % l - result += "}\n" - else: - result += emit_subnet(tree, network.values()[0], dhcphost, kickstart, - next_server=next_server) + if util.address(a) in n: + subnet[n] = s + pass + pass + pass + + for n in sorted(subnet): + for l in emit_subnet(tree, subnet[n], dhcphost, kickstart, + interface=interface, + next_server=next_server).split("\n"): + result += " %s\n" % l + pass + pass return result -def emit_subnet(tree, n, dhcphost, kickstart, next_server=None): +def emit_subnet(tree, subnet, dhcphost, kickstart, + interface=None, next_server=None): result = "" - + net = util.network(subnet) if next_server == None: next_server = dhcphost pass - netmask = aton(n.netmask[0]) - subnet = aton(n.network[0]) & netmask static = {} - dynamic = {} + dynamic = set() never = [] for ip in tree._host_._interface_._ip_: # Find all hosts that belong to this network - if ip.address[0]: - net = aton(ip.address[0]) & netmask - if net == subnet: + a = util.address(ip) + if a: + a = util.address(ip) + if a in net: if ip.dynamic[0] == dhcphost: # Dynamic address served by this host - dynamic[aton(ip.address[0])] = ip + dynamic.add(a) pass else: static[ip.name[0:]] = ip @@ -146,53 +160,55 @@ def emit_subnet(tree, n, dhcphost, kickstart, next_server=None): pass pass if ip.never[0]: - net = aton(ip.never[0]) & netmask - if net == subnet: + a = util.address(ip.never[0]) + if a in net: never.append(ip.ethernet[0:]) pass pass pass - - result += "subnet %s netmask %s {\n" % (n.network[0], n.netmask[0]) - result += " option subnet-mask %s;\n" % n.netmask[0] - if n.broadcast[0]: - result += " option broadcast-address %s;\n" % n.broadcast[0] - pass - if n.gateway[0]: - result += " option routers %s;\n" % n.gateway[0] + result += "subnet %s netmask %s {\n" % (net.network, net.netmask) + result += " option subnet-mask %s;\n" % net.netmask + result += " option broadcast-address %s;\n" % net.broadcast + if subnet.gateway[0]: + result += " option routers %s;\n" % subnet.gateway[0] pass - if n.domain[0]: - result += " option domain-name \"%s\";\n" % n.domain[0] + if subnet.domain[0]: + result += " option domain-name \"%s\";\n" % subnet.domain[0] pass - if n.name_servers[0]: - result += " option domain-name-servers %s;\n" % n.name_servers[0] + if subnet.name_servers[0]: + result += " option domain-name-servers %s;\n" % subnet.name_servers[0] pass - if n.ntp_servers[0]: - result += " option ntp-servers %s;\n" % (n.ntp_servers[0]) + if subnet.ntp_servers[0]: + result += " option ntp-servers %s;\n" % (subnet.ntp_servers[0]) pass # # Emit dynamic hosts # result += " default-lease-time 14400; # 4 hours\n" result += " max-lease-time 86400; # 1 day\n" - dk = dynamic.keys() - dk.sort() + for s in interface_subnets_matching(interface, net): + if s.first[0] and s.last[0]: + result += " range %s %s;\n" % (util.address(s.first[0]), + util.address(s.last[0])) + pass + pass + # FIXME: Old style dynamic... min = 0 max = 0 - for d in dk: + for d in sorted(dynamic): if d == max + 1: max = d pass else: if min: - result += " range %s %s;\n" % (ntoa(min), ntoa(max)) + result += " range %s %s;\n" % (min, max) pass min = d max = d pass pass if min: - result += " range %s %s;\n" % (ntoa(min), ntoa(max)) + result += " range %s %s;\n" % (min, max) pass result += " get-lease-hostnames true;\n" result += " use-host-decl-names on;\n" @@ -211,7 +227,7 @@ def emit_subnet(tree, n, dhcphost, kickstart, next_server=None): ip = i.address[0] if ether: assert ether.lower() == ether, "%s is not lower-case" % ether - result += " host %s.%s {\n"% (i.name[0:], n.domain[0]) + result += " host %s.%s {\n"% (i.name[0:], subnet.domain[0]) result += " hardware ethernet %s;\n" % ether result += " fixed-address %s;\n" % ip result += " option host-name \"%s\";\n" % i.name[0:] @@ -241,8 +257,52 @@ def emit_subnet(tree, n, dhcphost, kickstart, next_server=None): result += " ignore booting;\n" result += " }\n" pass - + +# result += CLASS_PXECLIENT % { 'next_server': next_server } + for s in interface_subnets_matching(interface, net): + if s.kickstart[0] != 'any': + continue + else: + continue + for i in filter(lambda i: i.ethernet[0] and i._kickstart_, + tree._host_._interface_): + e = i.ethernet[0] + assert e.lower() == e, "%s not lower-case" % e + result += " host %s {\n"% (i.name[0:]) + result += " hardware ethernet %s;\n" % e + result += " option host-name \"%s\";\n" % i.name[0:] + if not kickstart: + raise Exception("--kickstart needed for %s" % i.name[0:]) + kf = i._kickstart_[0].file[0] + result += " server-name \"%s\";\n" % dhcphost + result += " next-server %s;\n" % next_server + result += " if substring(option vendor-class-identifier, " + result += "0, 20) = \n" + result += " \"PXEClient:Arch:00000\" {\n" + result += " filename \"pxelinux.0\";\n" + result += " } else {\n" + result += " filename \"%s/%s\";\n" % (kickstart, kf) + result += " }\n" + result += " }\n" + pass + result += " if substring(option vendor-class-identifier, " + result += "0, 20) = \n" + result += " \"PXEClient:Arch:00000\" {\n" + result += " filename \"pxelinux.0\";\n" + result += " } else {\n" + result += " filename \"%s/default\";\n" % (kickstart) + result += " }\n" + break + result += " }\n" result += "}\n" return result + +def interface_subnets_matching(interface, net): + if not interface: + return + for s in filter(lambda s: util.network(s) == net, interface._subnet_): + yield s + pass + pass diff --git a/src/hostinfo/dhcpd_ipv6.py b/src/hostinfo/dhcpd_ipv6.py index b7bd2ca92ecf5471217f3a62909d637fa4467799..840f9229d3c3b18696ffaff801f25c744e853279 100755 --- a/src/hostinfo/dhcpd_ipv6.py +++ b/src/hostinfo/dhcpd_ipv6.py @@ -53,14 +53,14 @@ def emit_interface_ipv6(tree, interface, options): subnet = set() for ip in [ ip for ip in interface._ipv6_ if ip.address[0] ]: a = ipaddr.IPAddress(ip.address[0]) - subnet.update([ (s.prefix[0], s) for s in tree._subnet_ipv6_ + subnet.update([ (s.prefix[0], s) for s in tree._subnet_ if (s.prefix[0] and a in ipaddr.IPNetwork(s.prefix[0])) ]) pass for k,s in sorted(subnet): result = StringArray() dynamic = [ map(ipaddr.IPAddress, (r.first[0], r.last[0])) - for r in interface._dhcpserver_._subnet_ipv6_ + for r in interface._dhcpserver_._subnet_ if r.first[0] and r.last[0] and r.prefix[0] == s.prefix[0] ] result.append('subnet6 %s {' % ipaddr.IPNetwork(s.prefix[0]) ) result.extend(emit_subnet_ipv6(tree, s, dynamic, options).indent()) diff --git a/src/hostinfo/ifconfig.py b/src/hostinfo/ifconfig.py index 47c9050f462ee647b0f8771dfac828afef3e6bc7..cd7f8ca9434dbc52208f2667fe66cf06ce264b35 100755 --- a/src/hostinfo/ifconfig.py +++ b/src/hostinfo/ifconfig.py @@ -1,4 +1,5 @@ from hostinfo.util import aton +import hostinfo.util as util import subprocess import re import ipaddr @@ -113,11 +114,9 @@ def generate_ifcfgv6(tree, interface): pass else: address = [] - for ipv6 in interface._ipv6_: - if not ipv6.address[0]: - continue - a = ipaddr.IPAddress(ipv6.address[0]) - for s in [ ipaddr.IPNetwork(s.prefix[0]) for s in tree._subnet_ipv6_ ]: + for ipv6 in filter(util.address, interface._ipv6_): + a = util.address(ipv6) + for s in map(util.network, filter(util.network, tree._subnet_ )): if a in s: address.append('%s/%d' % (a, s.prefixlen)) pass diff --git a/src/hostinfo/named.py b/src/hostinfo/named.py index ae268ad9bf673ff5c90c22724b1afac56bab196d..54ed6d1fa37d051410f41ee3045fe07fa63b60b6 100755 --- a/src/hostinfo/named.py +++ b/src/hostinfo/named.py @@ -1,7 +1,33 @@ import copy import hostinfo.parser -from hostinfo.util import ntoa, aton, fqn, by_ip +from hostinfo.util import fqn, by_ip, network, address import ipaddr +import itertools + +def reverse_addr(addr): + if addr == None: + return None + if addr.version == 4: + def join(l): + return '.'.join(reversed(l))+'.in-addr.arpa' + if isinstance(addr, ipaddr._BaseNet): + assert addr.prefixlen % 8 == 0 + n = addr.prefixlen / 8 + return join(addr.exploded.split('.')[0:n]) + else: + return join(addr.exploded.split('.')) + pass + elif addr.version == 6: + def join(l): + return '.'.join(reversed(l))+'.ip6.arpa' + if isinstance(addr, ipaddr._BaseNet): + assert addr.prefixlen % 4 == 0 + n = addr.prefixlen / 4 + return join(map(None, addr.exploded.replace(':', ''))[0:n]) + else: + return join(map(None, addr.exploded.replace(':', ''))) + else: + raise Exception('Unknown address version %s' % addr) def generate(tree, host): # @@ -40,33 +66,41 @@ def generate(tree, host): conf += ' file "0.0.127.in-addr.arpa";\n' conf += '};\n' result.append(("named/%s" % rzone, reverse_local(tree, host))) + + forward = {} + reverse = {} for s in tree._subnet_: - fzone = s.domain[0] - if not done.has_key(fzone): - if forward(tree, s): - conf += "zone \"%s\" { \n" % fzone - conf += " type master; file \"hosts-%s\"; \n" % fzone - conf += "};\n" - result.append(("named/hosts-%s" % fzone, forward(tree, s))) - pass - done[fzone] = 1 + if not s.domain[0] in forward: + forward[s.domain[0]] = generate_forward(tree, s) pass - - if s.network[0]: - t = s.network[0].split(".")[0:3] - t.reverse() - rzone = ".".join(t) + ".in-addr.arpa" - if not done.has_key(rzone): - if reverse(tree, s): - conf += "zone \"%s\" { \n" % rzone - conf += " type master; file \"%s\"; \n" % rzone - conf += "};\n" - result.append(("named/%s" % rzone, reverse(tree, s))) - pass - done[rzone] = 1 - pass + r = reverse_addr(network(s)) + if r and not r in reverse: + reverse[r] = generate_reverse(tree, s) pass + + for s in tree._subnet_ipv6_: + if not s.domain[0] in forward: + forward[s.domain[0]] = generate_forward(tree, s) + pass + r = reverse_addr(network(s)) + if r and not r in reverse: + reverse[r] = generate_reverse(tree, s) + pass + pass + + for f in filter(lambda f: forward[f], sorted(forward)): + conf += "zone \"%s\" { \n" % f + conf += " type master; file \"hosts-%s\"; \n" % f + conf += "};\n" + result.append(("named/hosts-%s" % f, forward[f])) + pass + for r in filter(lambda r: reverse[r], sorted(reverse)): + conf += "zone \"%s\" { \n" % r + conf += " type master; file \"%s\"; \n" % r + conf += "};\n" + result.append(("named/%s" % r, reverse[r])) pass + result.append(("named.conf", conf)) return result @@ -113,20 +147,14 @@ def header(tree, domain, origin=None): result += ";\n" return result -def forward(tree, domain): +def generate_forward(tree, domain): result = header(tree, domain.domain[0], domain.domain[0]) if not result: return None net = [] for s in tree._subnet_: - if s.network[0] and s.domain[0] == domain.domain[0]: - net.append((aton(s.network[0]), aton(s.netmask[0]))) - pass - pass - net_ipv6 = [] - for s in tree._subnet_ipv6_: - if s.prefix[0] and s.domain[0] == domain.domain[0]: - net_ipv6.append(ipaddr.IPNetwork(s.prefix[0])) + if network(s) and s.domain[0] == domain.domain[0]: + net.append(network(s)) pass pass @@ -161,15 +189,17 @@ def forward(tree, domain): if not name in host: host[name] = [] pass - host[name].append((kind, value)) + if not (kind, value) in host[name]: + host[name].append((kind, value)) + pass pass - for i in tree._host_._interface_._ip_: + for i in filter(address, tree._host_._interface_._ip_): # Find all hosts that belong to this network - for (n, m) in net: - if i.address[0] and aton(i.address[0]) & m == n: + for n in net: + if address(i) in n: add_entry(i.name[1:], 'A', '%s' % i.address[0]) for a in i._alias_: - add_entry(a.name[0], 'CNAME', '%s' % i.name[1:]) + add_entry(a.name[0], 'A', '%s' % i.address[0]) pass for s in i._srv_: port = int(s.port[0] or 0) @@ -182,13 +212,17 @@ def forward(tree, domain): pass pass - for i in tree._host_._interface_._ipv6_: - for p in net_ipv6: - if ipaddr.IPAddress(i.address[0]) in p: + for i in filter(address, tree._host_._interface_._ipv6_): + for n in net: + if address(i) in n: add_entry(i.name[1:], 'AAAA', '%s' % i.address[0]) + for a in i._alias_: + add_entry(a.name[0], 'AAAA', '%s' % i.address[0]) + pass + pass pass pass - + for c in domain._cname_: # Emit cnames defined in subnet add_entry(c.alias[0], 'CNAME', '%s' % c.name[0]) @@ -203,26 +237,31 @@ def forward(tree, domain): return result -def reverse(tree, net): - t = net.network[0].split(".")[0:3] - t.reverse() - origin = ".".join(t) + ".in-addr.arpa" - result = header(tree, net.domain[0], origin) +def generate_reverse(tree, subnet): + net = network(subnet) + origin = reverse_addr(net) + result = header(tree, subnet.domain[0], origin) if not result: return None host = {} - m = aton(net.netmask[0]) - n = aton(net.network[0]) - for i in tree._host_._interface_._ip_: + for i in itertools.chain(tree._host_._interface_._ip_, + tree._host_._interface_._ipv6_): # Find all hosts that belong to this network - if i.address[0] and aton(i.address[0]) & m == n: - addr = aton(i.address[0]) & ~m - host[addr] = "PTR %s" % fqn(tree, i) - - hk = host.keys() - hk.sort() - for h in hk: - result += "%-16dIN %s\n" % (h, host[h]) + a = address(i) + if not a: + continue + if a in net: + r = reverse_addr(a).replace('.%s' % origin, '') + host[r] = "PTR %s" % fqn(tree, i) + pass + pass + def order(a, b): + def int16(v): + return int(v, 16) + return cmp(map(int16, a.split('.')), + map(int16, b.split('.'))) + for h in sorted(host, order): + result += "%-15s IN %s\n" % (h, host[h]) pass return result @@ -263,5 +302,5 @@ def reverse_local(tree, nameserver): t._add(h) break pass - return reverse(t, net) + return generate_reverse(t, net) diff --git a/src/hostinfo/util.py b/src/hostinfo/util.py index d0cccabe28269484690496b81442d62072de3239..24532f10ad5d66013ef62239032982a0ba518378 100755 --- a/src/hostinfo/util.py +++ b/src/hostinfo/util.py @@ -1,3 +1,23 @@ +import ipaddr +import itertools +import types + +def network(s): + if s.network[0] and s.netmask[0]: + return ipaddr.IPNetwork('%s/%s' % (s.network[0], s.netmask[0])) + elif s.prefix[0]: + return ipaddr.IPNetwork(s.prefix[0]) + else: + return None + +def address(ip): + if isinstance(ip, types.StringTypes): + return ipaddr.IPAddress(ip) + elif ip.address[0]: + return ipaddr.IPAddress(ip.address[0]) + else: + return None + def aton(addr): result = long(0) for s in addr.split('.'): @@ -12,14 +32,14 @@ def fqn(tree, host): name of host""" if host.name[0:].endswith('.'): return host.name[0:] - if host._tag == 'ip': - ip_addr = host.address[0] + if host._tag in [ 'ip', 'ipv6' ]: + ip_addr = address(host) elif host._tag == 'interface': for ip in host._ip_: if ip.alias[0] or ip.vlan[0]: continue if ip.address[0]: - ip_addr = ip.address[0] + ip_addr = address(ip) break pass pass @@ -28,9 +48,9 @@ def fqn(tree, host): if ip_addr: for s in tree._subnet_: - if (s.network[0] and - aton(ip_addr) & aton(s.netmask[0]) == aton(s.network[0])): - return "%s.%s." % (host.name[1:],s.domain[0]) + net = network(s) + if net and ip_addr in net: + return "%s.%s." % (host.name[1:], s.domain[0]) pass raise Exception("No subnet declaration for '%s' (%s)" % (host.name[0:], ip_addr))