source: genesis/tools/gformat.py@ 14053

Last change on this file since 14053 was 14053, checked in by rick, 7 years ago

Fix alias VLAN dhcp declaration not mapped to shared-network

While here make all entries-shared networks, since it does not harm anyways:

Note that even when the shared-network declaration is absent, an empty
one is created by the server to contain the subnet (and any scoped
parameters included in the subnet). For practical purposes, this means
that "stateless" DHCP clients, which are not tied to addresses (and
therefore subnets) will receive the same configuration as stateful
ones.

  • Property svn:executable set to *
  • Property svn:keywords set to Id
File size: 79.7 KB
RevLine 
[13935]1#!/usr/local/bin/python
[8242]2#
3# vim:ts=2:et:sw=2:ai
4# Wireless Leiden configuration generator, based on yaml files'
[9957]5#
6# XXX: This should be rewritten to make use of the ipaddr.py library.
7#
[10058]8# Sample apache configuration (mind the AcceptPathInfo!)
[13935]9# Alias /config /usr/local/www/config
10# <Directory /usr/local/www/config>
11# AddHandler cgi-script .py
12# Require all granted
[11426]13#
[13935]14# RewriteEngine on
15# RewriteCond %{REQUEST_FILENAME} !-f
16# RewriteRule ^(.*)$ gformat.py/$1 [L,QSA]
17# Options +FollowSymlinks +ExecCGI
[11426]18# </Directory>
19#
[12473]20# Package dependencies list:
[13842]21# FreeBSD:
22# pkg install devel/py-Jinja2 graphics/py-pyproj devel/py-yaml lang/python
23# Fedora:
24# yum install python-yaml pyproj proj-epsg python-jinja2
[11426]25#
[8242]26# Rick van der Zwet <info@rickvanderzwet.nl>
[9957]27#
[8622]28
29# Hack to make the script directory is also threated as a module search path.
30import sys
31import os
32sys.path.append(os.path.dirname(__file__))
33
[12246]34SVN = filter(os.path.isfile, ('/usr/local/bin/svn', '/usr/bin/svn'))[0]
[13984]35SVNVERSION = filter(os.path.isfile, ('/usr/local/bin/svnversion', '/usr/bin/svnversion'))[0]
[12245]36
[12570]37import argparse
[8242]38import cgi
[8267]39import cgitb
40import copy
[8242]41import glob
[11426]42import make_network_kml
43import math
[12570]44import pyproj
[11738]45import random
46import re
[8242]47import socket
48import string
49import subprocess
[12570]50import textwrap
[8242]51import time
[13985]52import traceback
[11426]53import urlparse
[11738]54
[8584]55from pprint import pprint
[13680]56from collections import defaultdict, OrderedDict
[13276]57from sys import stderr
[8575]58try:
59 import yaml
60except ImportError, e:
61 print e
62 print "[ERROR] Please install the python-yaml or devel/py-yaml package"
63 exit(1)
[8588]64
65try:
66 from yaml import CLoader as Loader
67 from yaml import CDumper as Dumper
68except ImportError:
69 from yaml import Loader, Dumper
70
[10584]71from jinja2 import Environment, Template
72def yesorno(value):
73 return "YES" if bool(value) else "NO"
74env = Environment()
75env.filters['yesorno'] = yesorno
76def render_template(datadump, template):
77 result = env.from_string(template).render(datadump)
78 # Make it look pretty to the naked eye, as jinja templates are not so
79 # friendly when it comes to whitespace formatting
80 ## Remove extra whitespace at end of line lstrip() style.
81 result = re.sub(r'\n[\ ]+','\n', result)
82 ## Include only a single newline between an definition and a comment
83 result = re.sub(r'(["\'])\n+([a-z]|\n#\n)',r'\1\n\2', result)
84 ## Remove extra newlines after single comment
85 result = re.sub(r'(#\n)\n+([a-z])',r'\1\2', result)
86 return result
[10110]87
[9697]88import logging
89logging.basicConfig(format='# %(levelname)s: %(message)s' )
90logger = logging.getLogger()
91logger.setLevel(logging.DEBUG)
[8242]92
[9283]93
[8948]94if os.environ.has_key('CONFIGROOT'):
95 NODE_DIR = os.environ['CONFIGROOT']
96else:
[13935]97 NODE_DIR = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) + '/../nodes'
[8242]98__version__ = '$Id: gformat.py 14053 2018-02-20 14:54:34Z rick $'
99
[13935]100CACHE_DIR = os.path.abspath(os.path.dirname(__file__))
101
[9283]102files = [
[8242]103 'authorized_keys',
104 'dnsmasq.conf',
[10410]105 'dhcpd.conf',
[8242]106 'rc.conf.local',
107 'resolv.conf',
[10069]108 'motd',
[10654]109 'ntp.conf',
[10705]110 'pf.hybrid.conf.local',
[13696]111 'unbound.wleiden.conf',
[10054]112 'wleiden.yaml',
[8242]113 ]
114
[8319]115# Global variables uses
[8323]116OK = 10
117DOWN = 20
118UNKNOWN = 90
[8257]119
[11426]120
[13680]121ileiden_proxies = OrderedDict()
[11503]122normal_proxies = []
[10860]123datadump_cache = {}
[11426]124interface_list_cache = {}
125rc_conf_local_cache = {}
[11503]126nameservers_cache = []
[13324]127relations_cache = None
[11533]128
[10887]129NO_DHCP = 0
130DHCP_CLIENT = 10
131DHCP_SERVER = 20
132def dhcp_type(item):
133 if not item.has_key('dhcp'):
134 return NO_DHCP
135 elif not item['dhcp']:
136 return NO_DHCP
137 elif item['dhcp'].lower() == 'client':
138 return DHCP_CLIENT
139 else:
[10889]140 # Validation Checks
141 begin,end = map(int,item['dhcp'].split('-'))
142 if begin >= end:
143 raise ValueError("DHCP Start >= DHCP End")
[10887]144 return DHCP_SERVER
145
[12473]146def etrs2rd(lat, lon):
147 p1 = pyproj.Proj(proj='latlon',datum='WGS84')
148 p2 = pyproj.Proj(init='EPSG:28992')
149 RDx, RDy = pyproj.transform(p1,p2,lon, lat)
150 return (RDx, RDy)
[10904]151
[12473]152def rd2etrs(RDx, RDy):
153 p1 = pyproj.Proj(init='EPSG:28992')
154 p2 = pyproj.Proj(proj='latlon',datum='WGS84')
155 lon, lat = pyproj.transform(p1,p2, RDx, RDy)
156 return (lat, lon)
[10904]157
158def get_yaml(item,add_version_info=True):
[10872]159 try:
160 """ Get configuration yaml for 'item'"""
161 if datadump_cache.has_key(item):
162 return datadump_cache[item].copy()
[10860]163
[10872]164 gfile = os.path.join(NODE_DIR,item,'wleiden.yaml')
[13861]165 global_rdr_file = os.path.join(NODE_DIR,'global_rdr_rules.yaml')
166 d = yaml.load(open(global_rdr_file, 'r'), Loader=Loader)
[8257]167
[10904]168 # Default values
169 datadump = {
170 'autogen_revision' : 'NOTFOUND',
171 'autogen_gfile' : gfile,
[13279]172 'service_proxy_ileiden' : False,
[10904]173 }
[10872]174 f = open(gfile, 'r')
175 datadump.update(yaml.load(f,Loader=Loader))
[13861]176 datadump['autogen_global_rdr_rules'] = d['global_rdr_rules']
[10872]177 if datadump['nodetype'] == 'Hybrid':
178 # Some values are defined implicitly
[13861]179 if datadump.has_key('rdr_host') and datadump['rdr_host'] and not datadump.has_key('service_incoming_rdr'):
[10872]180 datadump['service_incoming_rdr'] = True
181 # Use some boring defaults
182 defaults = {
183 'service_proxy_normal' : False,
184 'service_accesspoint' : True,
[11326]185 'service_incoming_rdr' : False,
[11538]186 'service_concentrator' : False,
[11326]187 'monitoring_group' : 'wleiden',
[10872]188 }
189 for (key,value) in defaults.iteritems():
190 if not datadump.has_key(key):
191 datadump[key] = value
192 f.close()
[10391]193
[10904]194 # Sometimes getting version information is useless or harmfull, like in the pre-commit hooks
195 if add_version_info:
[12245]196 p = subprocess.Popen([SVN, 'info', datadump['autogen_gfile']], stderr=subprocess.STDOUT, stdout=subprocess.PIPE)
[11624]197 lines = p.communicate()[0].split('\n')
198 if p.returncode == 0:
199 for line in lines:
200 if line:
201 (key, value) = line.split(': ')
202 datadump["autogen_" + key.lower().replace(' ','_')] = value
[10904]203
[10872]204 # Preformat certain needed variables for formatting and push those into special object
205 datadump['autogen_iface_keys'] = get_interface_keys(datadump)
[10391]206
[10872]207 wlan_count=0
208 try:
[13328]209 for key in get_interface_keys(datadump, True):
[10890]210 datadump[key]['autogen_ifbase'] = key.split('_')[1]
[13403]211 datadump[key]['autogen_vlan'] = False
212
[13618]213 datadump[key]['autogen_bridge_member'] = datadump[key].has_key('parent')
214 datadump[key]['autogen_bridge'] = datadump[key]['autogen_ifbase'].startswith('bridge')
[13930]215 datadump[key]['autogen_bridge_alias'] = datadump[key]['autogen_ifbase'].startswith('bridge') and '_alias' in key
[13618]216
[13705]217 if datadump[key].has_key('parent'):
218 if datadump[key].has_key('ip'):
219 raise ValueError("Interface bridge member cannot have IP assigned")
220 if datadump[key].has_key('dhcp') and datadump[key]['dhcp'] != False:
221 raise ValueError("Interface bridge member cannot have DHCP set")
222
[13618]223 if datadump[key].has_key('ip'):
224 datadump[key]['autogen_gateway'] = datadump[key]['ip'].split('/')[0]
225
[10872]226 if datadump[key]['type'] in ['11a', '11b', '11g', 'wireless']:
227 datadump[key]['autogen_ifname'] = 'wlan%i' % wlan_count
[13618]228 datadump[key]['autogen_iface'] = 'wlan%i' % wlan_count
[13929]229 datadump[key]['autogen_if_dhcp'] = 'wlan%i' % wlan_count
[10872]230 wlan_count += 1
231 else:
[13403]232 datadump[key]['autogen_ifname'] = '_'.join(key.split('_')[1:])
233 if len(key.split('_')) > 2 and key.split('_')[2].isdigit():
234 datadump[key]['autogen_vlan'] = key.split('_')[2]
[13618]235 datadump[key]['autogen_iface'] = '.'.join(key.split('_')[1:])
[14053]236 datadump[key]['autogen_if_dhcp'] = datadump[key]['autogen_ifname']
[13618]237 else:
238 datadump[key]['autogen_iface'] = '_'.join(key.split('_')[1:])
[14053]239 datadump[key]['autogen_if_dhcp'] = datadump[key]['autogen_ifbase']
240 print key,datadump[key]['autogen_iface'], datadump[key]['autogen_ifname'], datadump[key]['autogen_if_dhcp']
[13403]241
[13985]242 except Exception as exc:
243 exc.args = ("# Error while processing interface %s" % key,) + exc.args
[10872]244 raise
[10391]245
[13929]246 dhcp_interfaces = [datadump[key]['autogen_if_dhcp'] for key in datadump['autogen_iface_keys'] \
[10887]247 if dhcp_type(datadump[key]) == DHCP_SERVER]
248
[13929]249 datadump['autogen_dhcp_interfaces'] = [x.replace('_','.') for x in set(dhcp_interfaces)]
[10872]250 datadump['autogen_item'] = item
[10391]251
[10872]252 datadump['autogen_domain'] = datadump['domain'] if datadump.has_key('domain') else 'wleiden.net.'
[13405]253 datadump['autogen_fqdn'] = datadump['nodename'] + '.' + datadump['autogen_domain']
[10872]254 datadump_cache[item] = datadump.copy()
[13985]255 except Exception as exc:
256 exc.args = ("# Error while processing %s" % item,) + exc.args
[10872]257 raise
[10391]258 return datadump
259
260
261def store_yaml(datadump, header=False):
262 """ Store configuration yaml for 'item'"""
263 item = datadump['autogen_item']
264 gfile = os.path.join(NODE_DIR,item,'wleiden.yaml')
265
[10881]266 output = generate_wleiden_yaml(datadump, header)
267
[10391]268 f = open(gfile, 'w')
[10881]269 f.write(output)
[10391]270 f.close()
271
272
[10729]273def network(ip):
274 addr, mask = ip.split('/')
275 # Not parsing of these folks please
276 addr = parseaddr(addr)
277 mask = int(mask)
278 network = addr & ~((1 << (32 - mask)) - 1)
279 return network
280
[10391]281
[10729]282
[13324]283def make_relations():
[10270]284 """ Process _ALL_ yaml files to get connection relations """
[13324]285 global relations_cache
286
287 if relations_cache:
288 return relations_cache
289
[10729]290 errors = []
[10281]291 poel = defaultdict(list)
[10729]292
[13324]293 for host in get_hostlist():
294 datadump = get_yaml(host)
[10270]295 try:
[13328]296 for iface_key in get_interface_keys(datadump):
[13843]297 # Bridge members has no IP assigned
298 if 'parent' in datadump[iface_key] and not 'ip' in datadump[iface_key]:
299 continue
[10729]300 net_addr = network(datadump[iface_key]['ip'])
[13324]301 poel[net_addr] += [(host,datadump[iface_key].copy())]
[13843]302 except (KeyError, ValueError) as e:
303 errors.append("[FOUT] in '%s' interface '%s' (%s)" % (host,iface_key, type(e).__name__ + ': ' + str(e)))
[10270]304 continue
305
[13324]306 relations_cache = (poel, errors)
307 return relations_cache
[10270]308
[8267]309
[13324]310
[8321]311def valid_addr(addr):
312 """ Show which address is valid in which are not """
313 return str(addr).startswith('172.')
314
[8296]315def get_hostlist():
316 """ Combined hosts and proxy list"""
[13404]317 return sorted([os.path.basename(os.path.dirname(x)) for x in glob.glob("%s/*/wleiden.yaml" % (NODE_DIR))])
[8267]318
[8588]319def angle_between_points(lat1,lat2,long1,long2):
[9283]320 """
[8588]321 Return Angle in radians between two GPS coordinates
322 See: http://stackoverflow.com/questions/3809179/angle-between-2-gps-coordinates
323 """
324 dy = lat2 - lat1
[10729]325 dx = math.cos(lat1)*(long2 - long1)
[8588]326 angle = math.atan2(dy,dx)
327 return angle
[8267]328
[10729]329
330
[8588]331def angle_to_cd(angle):
332 """ Return Dutch Cardinal Direction estimation in 'one digit' of radian angle """
333
334 # For easy conversion get positive degree
335 degrees = math.degrees(angle)
[10729]336 abs_degrees = 360 + degrees if degrees < 0 else degrees
[8588]337
338 # Numbers can be confusing calculate from the 4 main directions
339 p = 22.5
[10729]340 if abs_degrees < p:
341 cd = "n"
342 elif abs_degrees < (90 - p):
343 cd = "no"
344 elif abs_degrees < (90 + p):
345 cd = "o"
346 elif abs_degrees < (180 - p):
347 cd = "zo"
348 elif abs_degrees < (180 + p):
349 cd = "z"
350 elif abs_degrees < (270 - p):
351 cd = "zw"
352 elif abs_degrees < (270 + p):
353 cd = "w"
354 elif abs_degrees < (360 - p):
355 cd = "nw"
[8588]356 else:
[10729]357 cd = "n"
358 return cd
[8588]359
360
[10729]361
362def cd_between_hosts(hostA, hostB, datadumps):
363 # Using RDNAP coordinates
364 dx = float(int(datadumps[hostA]['rdnap_x']) - int(datadumps[hostB]['rdnap_x'])) * -1
365 dy = float(int(datadumps[hostA]['rdnap_y']) - int(datadumps[hostB]['rdnap_y'])) * -1
366 return angle_to_cd(math.atan2(dx,dy))
367
368 # GPS coordinates seems to fail somehow
369 #latA = float(datadumps[hostA]['latitude'])
370 #latB = float(datadumps[hostB]['latitude'])
371 #lonA = float(datadumps[hostA]['longitude'])
372 #lonB = float(datadumps[hostB]['longitude'])
373 #return angle_to_cd(angle_between_points(latA, latB, lonA, lonB))
374
375
[8267]376def generate_title(nodelist):
[8257]377 """ Main overview page """
[13984]378 items = { \
379 'root' : ".",
380 'version' : subprocess.Popen([SVNVERSION, "%s/.." % NODE_DIR], stderr=subprocess.STDOUT, stdout=subprocess.PIPE).communicate()[0]
381 }
[10682]382 def fl(spaces, line):
383 return (' ' * spaces) + line + '\n'
384
[8267]385 output = """
[8257]386<html>
387 <head>
388 <title>Wireless leiden Configurator - GFormat</title>
389 <style type="text/css">
390 th {background-color: #999999}
391 tr:nth-child(odd) {background-color: #cccccc}
392 tr:nth-child(even) {background-color: #ffffff}
393 th, td {padding: 0.1em 1em}
394 </style>
395 </head>
396 <body>
397 <center>
[8259]398 <form type="GET" action="%(root)s">
[8257]399 <input type="hidden" name="action" value="update">
400 <input type="submit" value="Update Configuration Database (SVN)">
401 </form>
402 <table>
[13984]403 <caption><h3>Wireless Leiden Configurator - Revision %(version)s</h3></caption>
[8257]404 """ % items
[8242]405
[8296]406 for node in nodelist:
[8257]407 items['node'] = node
[10682]408 output += fl(5, '<tr>') + fl(7,'<td><a href="%(root)s/%(node)s">%(node)s</a></td>' % items)
[8257]409 for config in files:
410 items['config'] = config
[10682]411 output += fl(7,'<td><a href="%(root)s/%(node)s/%(config)s">%(config)s</a></td>' % items)
412 output += fl(5, "</tr>")
[8267]413 output += """
[8257]414 </table>
415 <hr />
416 <em>%s</em>
417 </center>
418 </body>
419</html>
420 """ % __version__
[8242]421
[8267]422 return output
[8257]423
424
[8267]425
426def generate_node(node):
[8257]427 """ Print overview of all files available for node """
[8267]428 return "\n".join(files)
[8242]429
[13935]430def generate_node_overview(host, datadump=False):
[10270]431 """ Print overview of all files available for node """
[13935]432 if not datadump:
433 datadump = get_yaml(host)
[10270]434 params = { 'host' : host }
435 output = "<em><a href='..'>Back to overview</a></em><hr />"
436 output += "<h2>Available files:</h2><ul>"
437 for cf in files:
438 params['cf'] = cf
[13935]439 output += '<li><a href="%(cf)s">%(cf)s</a></li>\n' % params
[10270]440 output += "</ul>"
[8257]441
[10270]442 # Generate and connection listing
443 output += "<h2>Connected To:</h2><ul>"
[10281]444 (poel, errors) = make_relations()
445 for network, hosts in poel.iteritems():
446 if host in [x[0] for x in hosts]:
447 if len(hosts) == 1:
448 # Single not connected interface
449 continue
450 for remote,ifacedump in hosts:
451 if remote == host:
452 # This side of the interface
453 continue
454 params = { 'remote': remote, 'remote_ip' : ifacedump['ip'] }
[13935]455 output += '<li><a href="../%(remote)s">%(remote)s</a> -- %(remote_ip)s</li>\n' % params
[10270]456 output += "</ul>"
[10281]457 output += "<h2>MOTD details:</h2><pre>" + generate_motd(datadump) + "</pre>"
[8257]458
[10270]459 output += "<hr /><em><a href='..'>Back to overview</a></em>"
460 return output
461
462
[10904]463def generate_header(datadump, ctag="#"):
[8242]464 return """\
[9283]465%(ctag)s
[8242]466%(ctag)s DO NOT EDIT - Automatically generated by 'gformat'
[9283]467%(ctag)s
[10904]468""" % { 'ctag' : ctag, 'date' : time.ctime(), 'host' : socket.gethostname(), 'revision' : datadump['autogen_revision'] }
[8242]469
[8257]470
471
[8242]472def parseaddr(s):
[8257]473 """ Process IPv4 CIDR notation addr to a (binary) number """
[8242]474 f = s.split('.')
475 return (long(f[0]) << 24L) + \
476 (long(f[1]) << 16L) + \
477 (long(f[2]) << 8L) + \
478 long(f[3])
479
[8257]480
481
[8242]482def showaddr(a):
[8257]483 """ Display IPv4 addr in (dotted) CIDR notation """
[8242]484 return "%d.%d.%d.%d" % ((a >> 24) & 0xff, (a >> 16) & 0xff, (a >> 8) & 0xff, a & 0xff)
485
[8257]486
[8584]487def is_member(ip, mask, canidate):
488 """ Return True if canidate is part of ip/mask block"""
[10729]489 ip_addr = parseaddr(ip)
490 ip_canidate = parseaddr(canidate)
[8584]491 mask = int(mask)
492 ip_addr = ip_addr & ~((1 << (32 - mask)) - 1)
493 ip_canidate = ip_canidate & ~((1 << (32 - mask)) - 1)
494 return ip_addr == ip_canidate
[8257]495
[8584]496
497
[10410]498def cidr2netmask(netmask):
[8257]499 """ Given a 'netmask' return corresponding CIDR """
[8242]500 return showaddr(0xffffffff & (0xffffffff << (32 - int(netmask))))
501
[10410]502def get_network(addr, mask):
503 return showaddr(parseaddr(addr) & ~((1 << (32 - int(mask))) - 1))
[8257]504
505
[10410]506def generate_dhcpd_conf(datadump):
507 """ Generate config file '/usr/local/etc/dhcpd.conf """
[13761]508 # Redundency support, in cause local DNS server is not running/responding.
[13861]509 datadump['autogen_backup_dns_servers'] = [x[1] for x in get_neighbours(datadump)]
[10904]510 output = generate_header(datadump)
[10410]511 output += Template("""\
512# option definitions common to all supported networks...
513option domain-name "dhcp.{{ autogen_fqdn }}";
514
515default-lease-time 600;
516max-lease-time 7200;
517
518# Use this to enble / disable dynamic dns updates globally.
519#ddns-update-style none;
520
521# If this DHCP server is the official DHCP server for the local
522# network, the authoritative directive should be uncommented.
523authoritative;
524
525# Use this to send dhcp log messages to a different log file (you also
526# have to hack syslog.conf to complete the redirection).
527log-facility local7;
528
[13906]529# UniFi Discovery Support
530option space ubnt;
531option ubnt.unifi-address code 1 = ip-address;
[10410]532#
[13906]533class "ubnt" {
534 match if substring (option vendor-class-identifier, 0, 4) = "ubnt";
535 option vendor-class-identifier "ubnt";
536 vendor-option-space ubnt;
537}
538
539#
[10410]540# Interface definitions
541#
[13524]542\n\n""").render(datadump)
[10410]543
[13524]544
545 # TODO: Use textwrap.fill instead
546 def indent(text, count):
547 return '\n'.join(map(lambda x: ' ' * count + x, text.split('\n')))
548
[13734]549 # Process DHCP blocks
[10734]550 dhcp_out = defaultdict(list)
[13328]551 for iface_key in get_interface_keys(datadump):
[13435]552 ifname = datadump[iface_key]['autogen_ifbase']
[14053]553 groupif = datadump[iface_key]['autogen_if_dhcp']
[10410]554 if not datadump[iface_key].has_key('comment'):
[10455]555 datadump[iface_key]['comment'] = None
[10410]556
[13618]557 if not datadump[iface_key].has_key('ip'):
558 continue
[13503]559
[13735]560 dhcp_out[groupif].append("## %(autogen_iface)s - %(comment)s\n" % datadump[iface_key])
[13618]561
[10410]562 (addr, mask) = datadump[iface_key]['ip'].split('/')
[10882]563 datadump[iface_key]['autogen_addr'] = addr
564 datadump[iface_key]['autogen_netmask'] = cidr2netmask(mask)
565 datadump[iface_key]['autogen_subnet'] = get_network(addr, mask)
[13524]566
[10889]567 if dhcp_type(datadump[iface_key]) != DHCP_SERVER:
[13735]568 dhcp_out[groupif].append(textwrap.dedent("""\
[13524]569 subnet %(autogen_subnet)s netmask %(autogen_netmask)s {
570 ### not autoritive
571 }
572 """ % datadump[iface_key]))
[10410]573 continue
574
[10889]575 (dhcp_start, dhcp_stop) = datadump[iface_key]['dhcp'].split('-')
[10410]576 dhcp_part = ".".join(addr.split('.')[0:3])
[10882]577 datadump[iface_key]['autogen_dhcp_start'] = dhcp_part + "." + dhcp_start
578 datadump[iface_key]['autogen_dhcp_stop'] = dhcp_part + "." + dhcp_stop
[13861]579 datadump[iface_key]['autogen_dns_servers'] = ','.join([datadump[iface_key]['autogen_addr']] + datadump['autogen_backup_dns_servers'])
[12480]580
581 # Assume the first 10 IPs could be used for static entries
582 if 'no_portal' in datadump:
583 fixed = 5
584 for mac in datadump['no_portal']:
[13735]585 dhcp_out[groupif].append(textwrap.dedent("""\
[13618]586 host fixed-%(ifname)s-%(fixed)s {
587 hardware ethernet %(mac)s;
588 fixed-address %(prefix)s.%(fixed)s;
589 }
590 """ % { 'ifname' : ifname, 'mac' : mac, 'prefix': dhcp_part, 'fixed' : fixed }))
[12480]591 fixed += 1
592
[13883]593 if 'dhcp_fixed' in datadump[iface_key]:
594 for (mac,addr,host) in datadump[iface_key]['dhcp_fixed']:
595 dhcp_out[groupif].append(textwrap.dedent("""\
596 host fixed-%(host)s {
597 hardware ethernet %(mac)s;
598 fixed-address %(addr)s;
599 }
600 """ % { 'host' : host, 'mac' : mac, 'addr' : addr}))
601
602
[13735]603 dhcp_out[groupif].append(textwrap.dedent("""\
[13618]604 subnet %(autogen_subnet)s netmask %(autogen_netmask)s {
605 range %(autogen_dhcp_start)s %(autogen_dhcp_stop)s;
606 option routers %(autogen_addr)s;
[13861]607 option domain-name-servers %(autogen_dns_servers)s;
[13906]608 option ubnt.unifi-address 172.17.107.10;
[13618]609 }
610 """ % datadump[iface_key]))
[13524]611
[13734]612 # Output the blocks in groups
[13733]613 for ifname,value in sorted(dhcp_out.iteritems()):
[14053]614 output += ("shared-network %s {\n" % ifname) + indent(''.join(value), 2).rstrip() + '\n}\n\n'
[10410]615 return output
616
617
618
[8242]619def generate_dnsmasq_conf(datadump):
[8257]620 """ Generate configuration file '/usr/local/etc/dnsmasq.conf' """
[10904]621 output = generate_header(datadump)
[10368]622 output += Template("""\
[9283]623# DHCP server options
[8242]624dhcp-authoritative
625dhcp-fqdn
[10391]626domain=dhcp.{{ autogen_fqdn }}
[8242]627domain-needed
628expand-hosts
[10120]629log-async=100
[8242]630
631# Low memory footprint
632cache-size=10000
633
[10368]634\n""").render(datadump)
635
[13328]636 for iface_key in get_interface_keys(datadump):
[8262]637 if not datadump[iface_key].has_key('comment'):
[10455]638 datadump[iface_key]['comment'] = None
[10890]639 output += "## %(autogen_ifname)s - %(comment)s\n" % datadump[iface_key]
[8242]640
[10889]641 if dhcp_type(datadump[iface_key]) != DHCP_SERVER:
[8242]642 output += "# not autoritive\n\n"
643 continue
644
[10889]645 (dhcp_start, dhcp_stop) = datadump[iface_key]['dhcp'].split('-')
646 (ip, cidr) = datadump[iface_key]['ip'].split('/')
647 datadump[iface_key]['autogen_netmask'] = cidr2netmask(cidr)
648
[8242]649 dhcp_part = ".".join(ip.split('.')[0:3])
[10882]650 datadump[iface_key]['autogen_dhcp_start'] = dhcp_part + "." + dhcp_start
651 datadump[iface_key]['autogen_dhcp_stop'] = dhcp_part + "." + dhcp_stop
[13618]652 output += "dhcp-range=%(autogen_iface)s,%(autogen_dhcp_start)s,%(autogen_dhcp_stop)s,%(autogen_netmask)s,24h\n\n" % datadump[iface_key]
[9283]653
[8242]654 return output
655
[8257]656
[13598]657class AutoVivification(dict):
658 """Implementation of perl's autovivification feature."""
659 def __getitem__(self, item):
660 try:
661 return dict.__getitem__(self, item)
662 except KeyError:
663 value = self[item] = type(self)()
664 return value
665
[10907]666def make_interface_list(datadump):
667 if interface_list_cache.has_key(datadump['autogen_item']):
668 return (interface_list_cache[datadump['autogen_item']])
669 # lo0 configuration:
670 # - 172.32.255.1/32 is the proxy.wleiden.net deflector
671 # - masterip is special as it needs to be assigned to at
672 # least one interface, so if not used assign to lo0
673 addrs_list = { 'lo0' : [("127.0.0.1/8", "LocalHost"), ("172.31.255.1/32","Proxy IP")] }
[13403]674 vlan_list = defaultdict(list)
[13618]675 bridge_list = defaultdict(list)
[13598]676 flags_if = AutoVivification()
[10907]677 dhclient_if = {'lo0' : False}
678
679 # XXX: Find some way of send this output nicely
680 output = ''
681
682 masterip_used = False
[13328]683 for iface_key in get_interface_keys(datadump):
[13618]684 if datadump[iface_key].has_key('ip') and datadump[iface_key]['ip'].startswith(datadump['masterip']):
[10907]685 masterip_used = True
686 break
687 if not masterip_used:
688 addrs_list['lo0'].append((datadump['masterip'] + "/32", 'Master IP Not used in interface'))
689
[13680]690 if 'serviceid' in datadump:
691 addrs_list['lo0'].append((datadump['serviceid'] + "/32", 'Lvrouted GW IP'))
692
[13328]693 for iface_key in get_interface_keys(datadump):
[10907]694 ifacedump = datadump[iface_key]
[13930]695 if ifacedump['autogen_bridge_alias']:
696 ifname = ifacedump['autogen_ifbase']
697 else:
698 ifname = ifacedump['autogen_ifname']
[10907]699
[13403]700 # If defined as vlan interface
701 if ifacedump['autogen_vlan']:
702 vlan_list[ifacedump['autogen_ifbase']].append(ifacedump['autogen_vlan'])
703
[13618]704 # If defined as bridge interface
705 if ifacedump['autogen_bridge_member']:
706 bridge_list[ifacedump['parent']].append(ifacedump['autogen_iface'])
707
[13930]708
[10907]709 # Flag dhclient is possible
[11739]710 if not dhclient_if.has_key(ifname) or dhclient_if[ifname] == False:
[11736]711 dhclient_if[ifname] = dhcp_type(ifacedump) == DHCP_CLIENT
[10907]712
[13598]713 # Ethernet address
714 if ifacedump.has_key('ether'):
715 flags_if[ifname]['ether'] = ifacedump['ether']
716
[10907]717 # Add interface IP to list
[13618]718 if ifacedump.has_key('ip'):
719 item = (ifacedump['ip'], ifacedump['comment'])
720 if addrs_list.has_key(ifname):
721 addrs_list[ifname].append(item)
722 else:
723 addrs_list[ifname] = [item]
[10907]724
725 # Alias only needs IP assignment for now, this might change if we
726 # are going to use virtual accesspoints
727 if "alias" in iface_key:
728 continue
729
730 # XXX: Might want to deduct type directly from interface name
731 if ifacedump['type'] in ['11a', '11b', '11g', 'wireless']:
732 # Default to station (client) mode
733 ifacedump['autogen_wlanmode'] = "sta"
734 if ifacedump['mode'] in ['master', 'master-wds', 'ap', 'ap-wds']:
735 ifacedump['autogen_wlanmode'] = "ap"
736
737 if not ifacedump.has_key('channel'):
738 if ifacedump['type'] == '11a':
739 ifacedump['channel'] = 36
740 else:
741 ifacedump['channel'] = 1
742
743 # Allow special hacks at the back like wds and stuff
744 if not ifacedump.has_key('extra'):
745 ifacedump['autogen_extra'] = 'regdomain ETSI country NL'
746 else:
747 ifacedump['autogen_extra'] = ifacedump['extra']
748
[13564]749 ifacedump['autogen_ssid_hex'] = '0x' + ''.join(x.encode('hex') for x in ifacedump['ssid'])
750
[10907]751 output += "wlans_%(autogen_ifbase)s='%(autogen_ifname)s'\n" % ifacedump
[13564]752 output += "# SSID is encoded in Hexadecimal to support spaces, plain text value is '%(ssid)s'\n" % ifacedump
[13525]753 output += ("create_args_%(autogen_ifname)s=\"wlanmode %(autogen_wlanmode)s mode " +\
[13564]754 "%(type)s ssid %(autogen_ssid_hex)s %(autogen_extra)s channel %(channel)s\"\n") % ifacedump
[13169]755 output += "\n"
[10907]756
757 elif ifacedump['type'] in ['ethernet', 'eth']:
758 # No special config needed besides IP
[13618]759 pass
760 elif ifacedump['type'] in ['vlan']:
761 # VLAN member has no special configuration
762 pass
[10907]763 else:
764 assert False, "Unknown type " + ifacedump['type']
765
[13618]766 store = (addrs_list, vlan_list, bridge_list, dhclient_if, flags_if, output)
[10907]767 interface_list_cache[datadump['autogen_item']] = store
768 return(store)
769
770
771
[13696]772def create_proxies_list():
773 if not ileiden_proxies or not normal_proxies:
[13762]774 # Placeholder for to-be-installed proxies, this will avoid updating the all
775 # nodes to include this new machine, yet due to an unbound issue, this list
776 # has to be kept small.
777
778 for i in range(1,20):
[13696]779 ileiden_proxies['172.31.254.%i' % i] = {'nodename' : 'unused'}
780
781 for host in get_hostlist():
782 hostdump = get_yaml(host)
783 if hostdump['status'] == 'up':
784 if hostdump['service_proxy_ileiden']:
785 ileiden_proxies[hostdump['serviceid']] = hostdump
786 if hostdump['service_proxy_normal']:
787 normal_proxies.append(hostdump)
788
789
790
[8242]791def generate_rc_conf_local(datadump):
[8257]792 """ Generate configuration file '/etc/rc.conf.local' """
[10860]793 item = datadump['autogen_item']
794 if rc_conf_local_cache.has_key(item):
795 return rc_conf_local_cache[item]
796
[10455]797 if not datadump.has_key('ileiden'):
798 datadump['autogen_ileiden_enable'] = False
799 else:
800 datadump['autogen_ileiden_enable'] = datadump['ileiden']
[10110]801
[10547]802 datadump['autogen_ileiden_enable'] = switchFormat(datadump['autogen_ileiden_enable'])
803
[13696]804 create_proxies_list()
[10585]805 datadump['autogen_ileiden_proxies'] = ileiden_proxies
806 datadump['autogen_normal_proxies'] = normal_proxies
807 datadump['autogen_normal_proxies_ips'] = ','.join([x['masterip'] for x in normal_proxies])
[10367]808 datadump['autogen_normal_proxies_names'] = ','.join([x['autogen_item'] for x in normal_proxies])
[13336]809 datadump['autogen_attached_devices'] = [x[2] for x in get_attached_devices(datadump)]
810 datadump['autogen_neighbours'] = [x[1] for x in get_neighbours(datadump)]
[10112]811
[10904]812 output = generate_header(datadump, "#");
[10584]813 output += render_template(datadump, """\
[10391]814hostname='{{ autogen_fqdn }}'
[10110]815location='{{ location }}'
816nodetype="{{ nodetype }}"
[9283]817
[10459]818#
819# Configured listings
820#
821captive_portal_whitelist=""
822{% if nodetype == "Proxy" %}
[10054]823#
[10459]824# Proxy Configuration
[10054]825#
[13358]826{% if gateway and service_proxy_ileiden -%}
[10110]827defaultrouter="{{ gateway }}"
828{% else -%}
829#defaultrouter="NOTSET"
830{% endif -%}
831internalif="{{ internalif }}"
[10112]832ileiden_enable="{{ autogen_ileiden_enable }}"
833gateway_enable="{{ autogen_ileiden_enable }}"
[10238]834pf_enable="yes"
[10302]835pf_rules="/etc/pf.conf"
[10455]836{% if autogen_ileiden_enable -%}
[10234]837pf_flags="-D ext_if={{ externalif }} -D int_if={{ internalif }} -D publicnat={80,443}"
[10238]838lvrouted_enable="{{ autogen_ileiden_enable }}"
839lvrouted_flags="-u -s s00p3rs3kr3t -m 28"
840{% else -%}
841pf_flags="-D ext_if={{ externalif }} -D int_if={{ internalif }} -D publicnat={0}"
[10310]842{% endif -%}
[10238]843{% if internalroute -%}
844static_routes="wleiden"
845route_wleiden="-net 172.16.0.0/12 {{ internalroute }}"
[10110]846{% endif -%}
[10054]847
[10584]848{% elif nodetype == "Hybrid" %}
849 #
850 # Hybrid Configuration
851 #
[13305]852 list_ileiden_proxies="
[13680]853 {% for serviceid,item in autogen_ileiden_proxies.iteritems() -%}
854 {{ "%-16s"|format(serviceid) }} # {{ item.nodename }}
[13305]855 {% endfor -%}
856 "
857 list_normal_proxies="
858 {% for item in autogen_normal_proxies -%}
[13680]859 {{ "%-16s"|format(item.serviceid) }} # {{ item.nodename }}
[13305]860 {% endfor -%}
861 "
862
[13896]863 {% if autogen_dhcp_interfaces -%}
[13618]864 captive_portal_interfaces="{{ autogen_dhcp_interfaces|join(',') }}"
[13896]865 {% else %}
866 captive_portal_interfaces="dummy"
867 {% endif %}
[10584]868 externalif="{{ externalif|default('vr0', true) }}"
869 masterip="{{ masterip }}"
[13398]870
871 {% if gateway and service_proxy_ileiden %}
872 defaultrouter="{{ gateway }}"
873 {% else %}
874 #defaultrouter="NOTSET"
875 {% endif %}
[10584]876
[13398]877 #
[10584]878 # Defined services
[13398]879 #
[10584]880 service_proxy_ileiden="{{ service_proxy_ileiden|yesorno }}"
881 service_proxy_normal="{{ service_proxy_normal|yesorno }}"
882 service_accesspoint="{{ service_accesspoint|yesorno }}"
[10748]883 service_incoming_rdr="{{ service_incoming_rdr|yesorno }}"
[11538]884 service_concentrator="{{ service_concentrator|yesorno }}"
[10459]885
[11540]886 {% if service_proxy_ileiden %}
[10584]887 pf_rules="/etc/pf.hybrid.conf"
[11540]888 {% if service_concentrator %}
[11541]889 pf_flags="-D ext_if=$externalif -D ext_if_net=$externalif:network -D inet_if=tun0 -D inet_ip='(tun0)' -D masterip=$masterip"
[11540]890 {% else %}
891 pf_flags="-D ext_if=$externalif -D ext_if_net=$externalif:network -D inet_if=$externalif -D inet_ip='($externalif:0)' -D masterip=$masterip"
892 {% endif %}
[10587]893 pf_flags="$pf_flags -D publicnat=80,443"
[12247]894 lvrouted_flags="$lvrouted_flags -g"
[10748]895 {% elif service_proxy_normal or service_incoming_rdr %}
[10649]896 pf_rules="/etc/pf.hybrid.conf"
[10587]897 pf_flags="-D ext_if=$externalif -D ext_if_net=$externalif:network -D masterip=$masterip"
[10649]898 pf_flags="$pf_flags -D publicnat=0"
[13305]899 lvrouted_flags="$lvrouted_flags -z `make_list "$list_ileiden_proxies" ","`"
[10649]900 named_setfib="1"
901 tinyproxy_setfib="1"
902 dnsmasq_setfib="1"
[10698]903 sshd_setfib="1"
[10584]904 {% else %}
[10983]905 named_auto_forward_only="YES"
[10584]906 pf_rules="/etc/pf.node.conf"
[10587]907 pf_flags=""
[13305]908 lvrouted_flags="$lvrouted_flags -z `make_list "$list_ileiden_proxies" ","`"
[10584]909 {% endif %}
[11539]910 {% if service_concentrator %}
911 # Do mind installing certificates is NOT done automatically for security reasons
912 openvpn_enable="YES"
913 openvpn_configfile="/usr/local/etc/openvpn/client.conf"
914 {% endif %}
[10459]915
[10584]916 {% if service_proxy_normal %}
917 tinyproxy_enable="yes"
918 {% else %}
919 pen_wrapper_enable="yes"
920 {% endif %}
[10460]921
[10584]922 {% if service_accesspoint %}
923 pf_flags="$pf_flags -D captive_portal_interfaces=$captive_portal_interfaces"
924 {% endif %}
[10459]925
[13927]926 {% if board == "ALIX2" or board == "net4801" %}
[10584]927 #
928 # ''Fat'' configuration, board has 256MB RAM
929 #
930 dnsmasq_enable="NO"
931 named_enable="YES"
[13696]932 unbound_enable="YES"
[10732]933 {% if autogen_dhcp_interfaces -%}
[10584]934 dhcpd_enable="YES"
[13618]935 dhcpd_flags="$dhcpd_flags {{ autogen_dhcp_interfaces|join(' ') }}"
[10732]936 {% endif -%}
[13419]937 {% elif board == "apu1d" %}
938 #
939 # ''Fat'' configuration, board has 1024MB RAM
940 #
941 dnsmasq_enable="NO"
[13912]942 unbound_enable="YES"
[13419]943 {% if autogen_dhcp_interfaces -%}
944 dhcpd_enable="YES"
[13618]945 dhcpd_flags="$dhcpd_flags {{ autogen_dhcp_interfaces|join(' ') }}"
[13419]946 {% endif -%}
[10584]947 {% endif -%}
[10110]948{% endif %}
949
[10584]950#
[13336]951# Script variables
952#
953attached_devices="{{ autogen_attached_devices|join(' ') }}"
954neighbours="{{ autogen_neighbours|join(' ') }}"
955
956
957#
[10584]958# Interface definitions
959#\n
960""")
961
[13618]962 (addrs_list, vlan_list, bridge_list, dhclient_if, flags_if, extra_ouput) = make_interface_list(datadump)
[13673]963 for iface, vlans in sorted(vlan_list.items()):
964 output += 'vlans_%s="%s"\n' % (iface, ' '.join(sorted(set(vlans))))
[8242]965
[13420]966 # VLAN Parent interfaces not containing a configuration should be marked active explcitly.
[13673]967 for iface in sorted(vlan_list.keys()):
[13420]968 if not iface in addrs_list.keys():
969 output += "ifconfig_%s='up'\n" % iface
970
[13503]971 output += "\n"
972
[13618]973 # Bridge configuration:
974 if bridge_list.keys():
975 output += "cloned_interfaces='%s'\n" % ' '.join(bridge_list.keys())
976
977 for iface in bridge_list.keys():
[13930]978 output += "ifconfig_%s='%s up'\n" % (iface, ' '.join(['addm %(iface)s private %(iface)s' % {'iface': x} for x in bridge_list[iface]]))
[13618]979
980 # Bridge member interfaces not containing a configuration should be marked active explcitly.
981 for _,members in bridge_list.items():
982 for iface in members:
983 if not iface in addrs_list.keys():
[13676]984 output += "ifconfig_%s='up'\n" % iface.replace('.','_')
[13618]985
986 output += "\n"
987
[13403]988 # Details like SSID
989 if extra_ouput:
990 output += extra_ouput.strip() + "\n"
991
[9283]992 # Print IP address which needs to be assigned over here
[8242]993 output += "\n"
994 for iface,addrs in sorted(addrs_list.iteritems()):
[10079]995 for addr, comment in sorted(addrs,key=lambda x: parseaddr(x[0].split('/')[0])):
[9808]996 output += "# %s || %s || %s\n" % (iface, addr, comment)
[8242]997
[10366]998 # Write DHCLIENT entry
[13503]999 if iface in dhclient_if and dhclient_if[iface]:
[10366]1000 output += "ifconfig_%s='SYNCDHCP'\n\n" % (iface)
[13565]1001 continue
[11739]1002
1003 # Make sure the external address is always first as this is needed in the
1004 # firewall setup
1005 addrs = sorted(
1006 [x for x in addrs if not '0.0.0.0' in x[0]],
1007 key=lambda x: x[0].split('.')[0],
1008 cmp=lambda x,y: cmp(1 if x == '172' else 0, 1 if y == '172' else 0)
1009 )
[13504]1010
[13599]1011 idx_offset = 0
[13618]1012 # Set MAC is required
1013 if flags_if[iface].has_key('ether'):
1014 output += "ifconfig_%s='link %s'\n" % (iface, flags_if[iface]['ether'])
1015 output += "ifconfig_%s_alias0='inet %s'\n" % (iface, addrs[0][0])
1016 idx_offset += 1
[13930]1017 elif iface in bridge_list:
1018 output += "ifconfig_%s_alias0='inet %s'\n" % (iface, addrs[0][0])
1019 idx_offset += 1
[13618]1020 else:
1021 output += "ifconfig_%s='inet %s'\n" % (iface, addrs[0][0])
[13599]1022
[13403]1023 for idx, addr in enumerate(addrs[1:]):
[13599]1024 output += "ifconfig_%s_alias%s='inet %s'\n" % (iface, idx + idx_offset, addr[0])
[13618]1025
[13403]1026 output += "\n"
[10366]1027
[10860]1028 rc_conf_local_cache[datadump['autogen_item']] = output
[8242]1029 return output
1030
[8257]1031
1032
[8317]1033def get_all_configs():
1034 """ Get dict with key 'host' with all configs present """
1035 configs = dict()
1036 for host in get_hostlist():
1037 datadump = get_yaml(host)
1038 configs[host] = datadump
1039 return configs
1040
1041
[13328]1042def get_interface_keys(config, extra=False):
[8319]1043 """ Quick hack to get all interface keys, later stage convert this to a iterator """
[13328]1044 elems = sorted([elem for elem in config.keys() if (elem.startswith('iface_') and not "lo0" in elem)])
1045 if extra == False:
1046 return filter(lambda x: not "extra" in x, elems)
1047 else:
1048 return elems
[8317]1049
[8319]1050
[8317]1051def get_used_ips(configs):
1052 """ Return array of all IPs used in config files"""
1053 ip_list = []
[8319]1054 for config in configs:
[8317]1055 ip_list.append(config['masterip'])
[13680]1056 if 'serviceid' in config:
1057 ip_list.append(config['serviceid'])
[13328]1058 for iface_key in get_interface_keys(config, True):
[8317]1059 l = config[iface_key]['ip']
1060 addr, mask = l.split('/')
1061 # Special case do not process
[8332]1062 if valid_addr(addr):
1063 ip_list.append(addr)
1064 else:
[9728]1065 logger.error("## IP '%s' in '%s' not valid" % (addr, config['nodename']))
[8317]1066 return sorted(ip_list)
1067
1068
1069
[10980]1070def get_nameservers(max_servers=None):
[10934]1071 if nameservers_cache:
[10980]1072 return nameservers_cache[0:max_servers]
[10934]1073
[13404]1074 for host in get_hostlist():
[10935]1075 hostdump = get_yaml(host)
[10937]1076 if hostdump['status'] == 'up' and (hostdump['service_proxy_ileiden'] or hostdump['service_proxy_normal']):
[13916]1077 nameservers_cache.append((hostdump['serviceid'], hostdump['nodename']))
[10934]1078
[10980]1079 return nameservers_cache[0:max_servers]
[10934]1080
1081
[13336]1082def get_neighbours(datadump):
[13618]1083 (addrs_list, _, _, dhclient_if, _, extra_ouput) = make_interface_list(datadump)
[13336]1084
1085 (poel, errors) = make_relations()
1086 table = []
1087 for iface,addrs in sorted(addrs_list.iteritems()):
1088 if iface in ['lo0']:
1089 continue
1090
1091 for addr, comment in sorted(addrs,key=lambda x: parseaddr(x[0].split('/')[0])):
1092 if not addr.startswith('172.'):
1093 # Avoid listing internet connections as pool
1094 continue
1095 for neighbour in poel[network(addr)]:
1096 if neighbour[0] != datadump['autogen_item']:
[13618]1097 table.append((iface, neighbour[1]['ip'].split('/')[0], neighbour[0] + " (" + neighbour[1]['autogen_iface'] + ")", neighbour[1]['comment']))
[13336]1098 return table
1099
1100
1101def get_attached_devices(datadump, url=False):
1102 table = []
1103 for iface_key in get_interface_keys(datadump, True):
1104 ifacedump = datadump[iface_key]
1105
[13618]1106 if not ifacedump.has_key('ns_ip'):
1107 continue
1108
1109 x_ip = ifacedump['ns_ip'].split('/')[0]
1110
[13336]1111 if 'mode' in ifacedump:
1112 x_mode = ifacedump['mode']
1113 else:
1114 x_mode = 'unknown'
1115
1116 if 'bridge_type' in ifacedump:
1117 device_type = ifacedump['bridge_type']
1118 else:
1119 device_type = 'Unknown'
1120
[13618]1121 table.append((ifacedump['autogen_iface'], x_mode, 'http://%s' % x_ip if url else x_ip, device_type))
[13336]1122 return table
1123
1124
[8242]1125def generate_resolv_conf(datadump):
[8257]1126 """ Generate configuration file '/etc/resolv.conf' """
[10468]1127 # XXX: This should properly going to be an datastructure soon
[10904]1128 datadump['autogen_header'] = generate_header(datadump, "#")
[10468]1129 datadump['autogen_edge_nameservers'] = ''
1130
[10934]1131
[10936]1132 for masterip,realname in get_nameservers():
[10934]1133 datadump['autogen_edge_nameservers'] += "nameserver %-15s # %s\n" % (masterip, realname)
1134
[10468]1135 return Template("""\
1136{{ autogen_header }}
[8242]1137search wleiden.net
[10468]1138
1139# Try local (cache) first
[10209]1140nameserver 127.0.0.1
[10468]1141
[10584]1142{% if service_proxy_normal or service_proxy_ileiden or nodetype == 'Proxy' -%}
[10053]1143nameserver 8.8.8.8 # Google Public NameServer
[13726]1144nameserver 64.6.64.6 # Verisign Public NameServer
[10468]1145{% else -%}
[10646]1146# START DYNAMIC LIST - updated by /tools/nameserver-shuffle
[10468]1147{{ autogen_edge_nameservers }}
1148{% endif -%}
1149""").render(datadump)
[10209]1150
[9283]1151
[8242]1152
[10654]1153def generate_ntp_conf(datadump):
1154 """ Generate configuration file '/etc/ntp.conf' """
1155 # XXX: This should properly going to be an datastructure soon
1156
[10904]1157 datadump['autogen_header'] = generate_header(datadump, "#")
[10654]1158 datadump['autogen_ntp_servers'] = ''
[13404]1159 for host in get_hostlist():
[10654]1160 hostdump = get_yaml(host)
1161 if hostdump['service_proxy_ileiden'] or hostdump['service_proxy_normal']:
[13405]1162 datadump['autogen_ntp_servers'] += "server %(masterip)-15s iburst maxpoll 9 # %(nodename)s\n" % hostdump
[10654]1163
1164 return Template("""\
1165{{ autogen_header }}
1166
1167{% if service_proxy_normal or service_proxy_ileiden or nodetype == 'Proxy' -%}
1168# Machine hooked to internet.
1169server 0.nl.pool.ntp.org iburst maxpoll 9
1170server 1.nl.pool.ntp.org iburst maxpoll 9
1171server 2.nl.pool.ntp.org iburst maxpoll 9
1172server 3.nl.pool.ntp.org iburst maxpoll 9
1173{% else -%}
1174# Local Wireless Leiden NTP Servers.
1175server 0.pool.ntp.wleiden.net iburst maxpoll 9
1176server 1.pool.ntp.wleiden.net iburst maxpoll 9
1177server 2.pool.ntp.wleiden.net iburst maxpoll 9
1178server 3.pool.ntp.wleiden.net iburst maxpoll 9
1179
1180# All the configured NTP servers
1181{{ autogen_ntp_servers }}
1182{% endif %}
1183
1184# If a server loses sync with all upstream servers, NTP clients
1185# no longer follow that server. The local clock can be configured
1186# to provide a time source when this happens, but it should usually
1187# be configured on just one server on a network. For more details see
1188# http://support.ntp.org/bin/view/Support/UndisciplinedLocalClock
1189# The use of Orphan Mode may be preferable.
1190#
1191server 127.127.1.0
1192fudge 127.127.1.0 stratum 10
1193""").render(datadump)
1194
1195
[10705]1196def generate_pf_hybrid_conf_local(datadump):
1197 """ Generate configuration file '/etc/pf.hybrid.conf.local' """
[10904]1198 datadump['autogen_header'] = generate_header(datadump, "#")
[13861]1199 if datadump['service_incoming_rdr']:
1200 datadump['global_rdr_rules'] = datadump['autogen_global_rdr_rules']
[10705]1201 return Template("""\
1202{{ autogen_header }}
[10654]1203
[10705]1204# Redirect some internal facing services outside (7)
[13861]1205# INFO: {{ global_rdr_rules|count }} global_rdr_rules active on this node.
[13863]1206{% for protocol, src_port,dest_ip,dest_port,comment in global_rdr_rules -%}
[13886]1207rdr on $ext_if inet proto {{ protocol }} from any to $ext_if port {{ src_port }} tag SRV -> {{ "%-14s"|format(dest_ip) }} port {{ "%4s"|format(dest_port) }} # {{ comment }}
[13861]1208{% endfor -%}
1209# INFO: {{ rdr_rules|count }} node specific rdr_rules defined.
[13863]1210{% for protocol, src_port,dest_ip,dest_port,comment in rdr_rules -%}
[13886]1211rdr on $ext_if inet proto {{ protocol }} from any to $ext_if port {{ src_port }} tag SRV -> {{ "%-14s"|format(dest_ip) }} port {{ "%4s"|format(dest_port) }} # {{ comment }}
[10714]1212{% endfor -%}
[10705]1213""").render(datadump)
1214
[13696]1215def generate_unbound_wleiden_conf(datadump):
1216 """ Generate configuration file '/usr/local/etc/unbound.wleiden.conf' """
1217 datadump['autogen_header'] = generate_header(datadump, "#")
1218
1219 autogen_ips = []
1220 (addrs_list, _, _, dhclient_if, _, extra_ouput) = make_interface_list(datadump)
1221 for iface,addrs in sorted(addrs_list.iteritems()):
1222 for addr, comment in sorted(addrs,key=lambda x: parseaddr(x[0].split('/')[0])):
1223 if addr.startswith('172'):
1224 autogen_ips.append((addr.split('/')[0], comment))
1225 datadump['autogen_ips'] = autogen_ips
1226
1227 create_proxies_list()
1228 datadump['autogen_ileiden_proxies'] = ileiden_proxies
1229 return Template("""\
1230{{ autogen_header }}
1231
1232server:
[13911]1233 ## Static definitions fail to start on systems with broken ue(4) interfaces
[13696]1234{%- for ip,comment in autogen_ips %}
[13911]1235 # interface: {{ "%-16s"|format(ip) }} # {{ comment }}
[13696]1236{%- endfor %}
[13911]1237 ## Enabling wildcard matching as work-around
1238 interface: 0.0.0.0
1239 interface: ::0
[13696]1240
1241forward-zone:
1242 name: '.'
1243{%- if service_proxy_ileiden %}
1244 forward-addr: 8.8.8.8 # Google DNS A
1245 forward-addr: 8.8.4.4 # Google DNS B
1246 forward-addr: 208.67.222.222 # OpenDNS DNS A
1247 forward-addr: 208.67.220.220 # OpenDNS DNS B
1248{% else -%}
[13911]1249{%- for serviceid,item in autogen_ileiden_proxies.iteritems() %}
1250 {%- if loop.index <= 5 %}
[13696]1251 forward-addr: {{ "%-16s"|format(serviceid) }} # {{ item.nodename }}
[13911]1252 {%- endif %}
[13696]1253{%- endfor %}
[13911]1254{%- endif %}
[13696]1255""").render(datadump)
1256
[10069]1257def generate_motd(datadump):
1258 """ Generate configuration file '/etc/motd' """
[10568]1259 output = Template("""\
[10627]1260FreeBSD run ``service motd onestart'' to make me look normal
[8242]1261
[10568]1262 WWW: {{ autogen_fqdn }} - http://www.wirelessleiden.nl
1263 Loc: {{ location }}
[8257]1264
[10568]1265Services:
[13927]1266{% if board == "ALIX2" or board == "net4801" -%}
[10906]1267{{" -"}} Core Node ({{ board }})
[10568]1268{% else -%}
[10906]1269{{" -"}} Hulp Node ({{ board }})
[10568]1270{% endif -%}
[10584]1271{% if service_proxy_normal -%}
[10906]1272{{" -"}} Normal Proxy
[10568]1273{% endif -%}
[10584]1274{% if service_proxy_ileiden -%}
[10906]1275{{" -"}} iLeiden Proxy
[10748]1276{% endif -%}
1277{% if service_incoming_rdr -%}
[10906]1278{{" -"}} Incoming port redirects
[10568]1279{% endif %}
[10626]1280Interlinks:\n
[10568]1281""").render(datadump)
[10069]1282
[13327]1283
1284 def make_table(table):
1285 if not table:
1286 return " - none\n"
1287 else:
1288 lines = ""
1289 col_width = [max(len(x) for x in col) for col in zip(*table)]
1290 for row in table:
[13618]1291 # replace('_','.') is a hack to convert vlan interfaces to proper named interfaces
1292 lines += " - " + " || ".join("{:{}}".format(x.replace('_','.'), col_width[i]) for i, x in enumerate(row)) + "\n"
[13327]1293 return lines
1294
[13618]1295 (addrs_list, vlan_list, bridge_list, dhclient_if, flags_if, extra_ouput) = make_interface_list(datadump)
[13327]1296 table = []
[10907]1297 for iface,addrs in sorted(addrs_list.iteritems()):
1298 if iface in ['lo0']:
1299 continue
1300 for addr, comment in sorted(addrs,key=lambda x: parseaddr(x[0].split('/')[0])):
[13327]1301 table.append((iface, addr, comment))
[10907]1302
[13327]1303 output += make_table(table)
[10907]1304 output += '\n'
[10069]1305 output += """\
[13327]1306Attached devices:
[10069]1307"""
[13336]1308 output += make_table(get_attached_devices(datadump, url=True))
[13324]1309 output += '\n'
1310 output += """\
1311Available neighbours:
1312"""
[13336]1313 output += make_table(get_neighbours(datadump))
[13324]1314
[10069]1315 return output
1316
1317
[8267]1318def format_yaml_value(value):
1319 """ Get yaml value in right syntax for outputting """
1320 if isinstance(value,str):
[10049]1321 output = '"%s"' % value
[8267]1322 else:
1323 output = value
[9283]1324 return output
[8267]1325
1326
1327
1328def format_wleiden_yaml(datadump):
[8242]1329 """ Special formatting to ensure it is editable"""
[9283]1330 output = "# Genesis config yaml style\n"
[8262]1331 output += "# vim:ts=2:et:sw=2:ai\n"
[8242]1332 output += "#\n"
1333 iface_keys = [elem for elem in datadump.keys() if elem.startswith('iface_')]
1334 for key in sorted(set(datadump.keys()) - set(iface_keys)):
[10714]1335 if key == 'rdr_rules':
1336 output += '%-10s:\n' % 'rdr_rules'
1337 for rdr_rule in datadump[key]:
1338 output += '- %s\n' % rdr_rule
1339 else:
1340 output += "%-10s: %s\n" % (key, format_yaml_value(datadump[key]))
[9283]1341
[8242]1342 output += "\n\n"
[9283]1343
[10881]1344 # Format (key, required)
1345 key_order = (
1346 ('comment', True),
[13618]1347 ('parent', False),
1348 ('ip', False),
[14024]1349 ('ipv6', False),
[13601]1350 ('ether', False),
[10881]1351 ('desc', True),
1352 ('sdesc', True),
1353 ('mode', True),
1354 ('type', True),
1355 ('extra_type', False),
1356 ('channel', False),
1357 ('ssid', False),
[13079]1358 ('wlan_mac', False),
[10881]1359 ('dhcp', True),
[13884]1360 ('dhcp_fixed', False),
[10881]1361 ('compass', False),
1362 ('distance', False),
1363 ('ns_ip', False),
[13246]1364 ('repeater_ip', False),
[10881]1365 ('bullet2_ip', False),
1366 ('ns_mac', False),
1367 ('bullet2_mac', False),
1368 ('ns_type', False),
[10892]1369 ('bridge_type', False),
[14035]1370 ('encrypted', False),
[10881]1371 ('status', True),
1372 )
[8272]1373
[8242]1374 for iface_key in sorted(iface_keys):
[10881]1375 try:
1376 remainder = set(datadump[iface_key].keys()) - set([x[0] for x in key_order])
1377 if remainder:
1378 raise KeyError("invalid keys: %s" % remainder)
[8242]1379
[10881]1380 output += "%s:\n" % iface_key
1381 for key,required in key_order:
1382 if datadump[iface_key].has_key(key):
1383 output += " %-11s: %s\n" % (key, format_yaml_value(datadump[iface_key][key]))
1384 output += "\n\n"
[13985]1385 except Exception as exc:
1386 exc.args = ("# Error while processing interface %s" % iface_key,) + exc.args
[10881]1387 raise
1388
[8242]1389 return output
1390
1391
[8257]1392
[10067]1393def generate_wleiden_yaml(datadump, header=True):
[8267]1394 """ Generate (petty) version of wleiden.yaml"""
[10904]1395 output = generate_header(datadump, "#") if header else ''
1396
[10053]1397 for key in datadump.keys():
1398 if key.startswith('autogen_'):
1399 del datadump[key]
[10054]1400 # Interface autogen cleanups
1401 elif type(datadump[key]) == dict:
1402 for key2 in datadump[key].keys():
1403 if key2.startswith('autogen_'):
1404 del datadump[key][key2]
1405
[8267]1406 output += format_wleiden_yaml(datadump)
1407 return output
1408
[12349]1409def generate_nanostation_config(datadump, iface, ns_type):
[12441]1410 #TODO(rvdz): Make sure the proper nanostation IP and subnet is set
1411 datadump['iface_%s' % iface]['ns_ip'] = datadump['iface_%s' % iface]['ns_ip'].split('/')[0]
1412
[12349]1413 datadump.update(datadump['iface_%s' % iface])
[8267]1414
[12349]1415 return open(os.path.join(os.path.dirname(__file__), 'ns5m.cfg.tmpl'),'r').read() % datadump
1416
[8588]1417def generate_yaml(datadump):
1418 return generate_config(datadump['nodename'], "wleiden.yaml", datadump)
[8267]1419
[8588]1420
[9283]1421
[8298]1422def generate_config(node, config, datadump=None):
[8257]1423 """ Print configuration file 'config' of 'node' """
[8267]1424 output = ""
[8242]1425 try:
1426 # Load config file
[8298]1427 if datadump == None:
1428 datadump = get_yaml(node)
[9283]1429
[8242]1430 if config == 'wleiden.yaml':
[8267]1431 output += generate_wleiden_yaml(datadump)
1432 elif config == 'authorized_keys':
[10051]1433 f = open(os.path.join(NODE_DIR,"global_keys"), 'r')
[8267]1434 output += f.read()
[12433]1435 node_keys = os.path.join(NODE_DIR,node,'authorized_keys')
1436 # Fetch local keys if existing
1437 if os.path.exists(node_keys):
1438 output += open(node_keys, 'r').read()
[8242]1439 f.close()
1440 elif config == 'dnsmasq.conf':
[10281]1441 output += generate_dnsmasq_conf(datadump)
[10410]1442 elif config == 'dhcpd.conf':
1443 output += generate_dhcpd_conf(datadump)
[8242]1444 elif config == 'rc.conf.local':
[10281]1445 output += generate_rc_conf_local(datadump)
[8242]1446 elif config == 'resolv.conf':
[10281]1447 output += generate_resolv_conf(datadump)
[10654]1448 elif config == 'ntp.conf':
1449 output += generate_ntp_conf(datadump)
[10069]1450 elif config == 'motd':
[10281]1451 output += generate_motd(datadump)
[10705]1452 elif config == 'pf.hybrid.conf.local':
1453 output += generate_pf_hybrid_conf_local(datadump)
[13696]1454 elif config == 'unbound.wleiden.conf':
1455 output += generate_unbound_wleiden_conf(datadump)
[12349]1456 elif config.startswith('vr'):
1457 interface, ns_type = config.strip('.yaml').split('-')
1458 output += generate_nanostation_config(datadump, interface, ns_type)
[8242]1459 else:
[9283]1460 assert False, "Config not found!"
[8242]1461 except IOError, e:
[8267]1462 output += "[ERROR] Config file not found"
1463 return output
[8242]1464
1465
[8257]1466
[13937]1467def generate_static(output_dir, logging=True):
1468 items = {'output_dir' : output_dir}
1469 for node in get_hostlist():
1470 items['node'] = node
1471 items['wdir'] = "%(output_dir)s/%(node)s" % items
1472 if not os.path.isdir(items['wdir']):
1473 os.makedirs(items['wdir'])
1474 datadump = get_yaml(node)
1475 f = open("%(wdir)s/index.html" % items, "w")
1476 f.write(generate_node_overview(items['node'], datadump))
1477 f.close()
1478 for config in files:
1479 items['config'] = config
1480 if logging: logger.info("## Generating %(node)s %(config)s" % items)
1481 f = open("%(wdir)s/%(config)s" % items, "w")
1482 f.write(generate_config(node, config, datadump))
1483 f.close()
1484
1485
1486
[11426]1487def process_cgi_request(environ=os.environ):
[8258]1488 """ When calling from CGI """
[11426]1489 response_headers = []
1490 content_type = 'text/plain'
1491
[8258]1492 # Update repository if requested
[11427]1493 form = urlparse.parse_qs(environ['QUERY_STRING']) if environ.has_key('QUERY_STRING') else None
1494 if form and form.has_key("action") and "update" in form["action"]:
[13986]1495 refresh_rate = 5
[11426]1496 output = "[INFO] Updating subverion, please wait...\n"
[14045]1497 old_version = subprocess.Popen([SVNVERSION, '-c', "%s/.." % NODE_DIR], stderr=subprocess.STDOUT, stdout=subprocess.PIPE).communicate()[0]
[12245]1498 output += subprocess.Popen([SVN, 'cleanup', "%s/.." % NODE_DIR], stderr=subprocess.STDOUT, stdout=subprocess.PIPE).communicate()[0]
1499 output += subprocess.Popen([SVN, 'up', "%s/.." % NODE_DIR], stderr=subprocess.STDOUT, stdout=subprocess.PIPE).communicate()[0]
[14045]1500 new_version = subprocess.Popen([SVNVERSION, '-c', "%s/.." % NODE_DIR], stderr=subprocess.STDOUT, stdout=subprocess.PIPE).communicate()[0]
1501 if old_version != new_version:
1502 try:
1503 generate_static(CACHE_DIR, False)
1504 except:
1505 output += traceback.format_exc()
1506 refresh_rate = 120
1507 pass
[13986]1508 output += "[INFO] All done, redirecting in %s seconds" % refresh_rate
[11426]1509 response_headers += [
[13986]1510 ('Refresh', '%s; url=.' % refresh_rate),
[11426]1511 ]
1512 else:
[13935]1513 base_uri = environ['REQUEST_URI']
1514 uri = base_uri.strip('/').split('/')[1:]
[9283]1515
[11426]1516 output = "Template Holder"
1517 if base_uri.endswith('/create/network.kml'):
1518 content_type='application/vnd.google-earth.kml+xml'
1519 output = make_network_kml.make_graph()
[11444]1520 elif base_uri.endswith('/api/get/nodeplanner.json'):
1521 content_type='application/json'
1522 output = make_network_kml.make_nodeplanner_json()
[13935]1523 elif not uri:
[11426]1524 if is_text_request(environ):
1525 output = '\n'.join(get_hostlist())
1526 else:
1527 content_type = 'text/html'
1528 output = generate_title(get_hostlist())
1529 elif len(uri) == 1:
1530 if is_text_request(environ):
1531 output = generate_node(uri[0])
1532 else:
1533 content_type = 'text/html'
[13935]1534 output = open(os.path.join(CACHE_DIR, uri[0], 'index.html'), 'r').read()
[11426]1535 elif len(uri) == 2:
1536 output = generate_config(uri[0], uri[1])
1537 else:
1538 assert False, "Invalid option"
[9283]1539
[11426]1540 # Return response
1541 response_headers += [
1542 ('Content-type', content_type),
1543 ('Content-Length', str(len(output))),
1544 ]
1545 return(response_headers, str(output))
[10270]1546
[10681]1547
[10264]1548def make_dns(output_dir = 'dns', external = False):
[8588]1549 items = dict()
[8598]1550
[8588]1551 # hostname is key, IP is value
[10642]1552 wleiden_zone = defaultdict(list)
[8588]1553 wleiden_cname = dict()
[8598]1554
[8588]1555 pool = dict()
1556 for node in get_hostlist():
1557 datadump = get_yaml(node)
[9283]1558
[13405]1559 fqdn = datadump['nodename']
[10730]1560
1561 if datadump.has_key('rdr_host'):
1562 remote_target = datadump['rdr_host']
1563 elif datadump.has_key('remote_access') and datadump['remote_access']:
1564 remote_target = datadump['remote_access'].split(':')[0]
1565 else:
1566 remote_target = None
[8588]1567
[10730]1568 if remote_target:
1569 try:
1570 parseaddr(remote_target)
1571 wleiden_zone[datadump['nodename'] + '.gw'].append((remote_target, False))
1572 except (IndexError, ValueError):
1573 wleiden_cname[datadump['nodename'] + '.gw'] = remote_target + '.'
1574
1575
[10655]1576 wleiden_zone[fqdn].append((datadump['masterip'], True))
[8588]1577
[8598]1578 # Hacking to get proper DHCP IPs and hostnames
[8588]1579 for iface_key in get_interface_keys(datadump):
[10890]1580 iface_name = iface_key.replace('_','-')
[13647]1581 if 'ip' in datadump[iface_key]:
1582 (ip, cidr) = datadump[iface_key]['ip'].split('/')
[8588]1583 try:
1584 (dhcp_start, dhcp_stop) = datadump[iface_key]['dhcp'].split('-')
[10882]1585 datadump[iface_key]['autogen_netmask'] = cidr2netmask(cidr)
[8588]1586 dhcp_part = ".".join(ip.split('.')[0:3])
1587 if ip != datadump['masterip']:
[13648]1588 wleiden_zone["dhcp-gateway-%s.%s" % (iface_name, fqdn)].append((ip, True))
[8588]1589 for i in range(int(dhcp_start), int(dhcp_stop) + 1):
[10655]1590 wleiden_zone["dhcp-%s-%s.%s" % (i, iface_name, fqdn)].append(("%s.%s" % (dhcp_part, i), True))
[10825]1591 except (AttributeError, ValueError, KeyError):
[8588]1592 # First push it into a pool, to indentify the counter-part later on
1593 addr = parseaddr(ip)
[10461]1594 cidr = int(cidr)
1595 addr = addr & ~((1 << (32 - cidr)) - 1)
[9283]1596 if pool.has_key(addr):
[8588]1597 pool[addr] += [(iface_name, fqdn, ip)]
[9283]1598 else:
[8588]1599 pool[addr] = [(iface_name, fqdn, ip)]
1600 continue
1601
[9286]1602
1603
[9957]1604 # WL uses an /29 to configure an interface. IP's are ordered like this:
[9958]1605 # MasterA (.1) -- DeviceA (.2) <<>> DeviceB (.3) --- SlaveB (.4)
[9957]1606
1607 sn = lambda x: re.sub(r'(?i)^cnode','',x)
1608
[8598]1609 # Automatic naming convention of interlinks namely 2 + remote.lower()
[8588]1610 for (key,value) in pool.iteritems():
[9958]1611 # Make sure they are sorted from low-ip to high-ip
1612 value = sorted(value, key=lambda x: parseaddr(x[2]))
1613
[8588]1614 if len(value) == 1:
1615 (iface_name, fqdn, ip) = value[0]
[10655]1616 wleiden_zone["2unused-%s.%s" % (iface_name, fqdn)].append((ip, True))
[9957]1617
1618 # Device DNS names
1619 if 'cnode' in fqdn.lower():
[10655]1620 wleiden_zone["d-at-%s.%s" % (iface_name, fqdn)].append((showaddr(parseaddr(ip) + 1), False))
1621 wleiden_cname["d-at-%s.%s" % (iface_name,sn(fqdn))] = "d-at-%s.%s" % ((iface_name, fqdn))
[9957]1622
[8588]1623 elif len(value) == 2:
1624 (a_iface_name, a_fqdn, a_ip) = value[0]
1625 (b_iface_name, b_fqdn, b_ip) = value[1]
[10655]1626 wleiden_zone["2%s.%s" % (b_fqdn,a_fqdn)].append((a_ip, True))
1627 wleiden_zone["2%s.%s" % (a_fqdn,b_fqdn)].append((b_ip, True))
[9957]1628
1629 # Device DNS names
1630 if 'cnode' in a_fqdn.lower() and 'cnode' in b_fqdn.lower():
[10655]1631 wleiden_zone["d-at-%s.%s" % (a_iface_name, a_fqdn)].append((showaddr(parseaddr(a_ip) + 1), False))
1632 wleiden_zone["d-at-%s.%s" % (b_iface_name, b_fqdn)].append((showaddr(parseaddr(b_ip) - 1), False))
[9957]1633 wleiden_cname["d-at-%s.%s" % (a_iface_name,sn(a_fqdn))] = "d-at-%s.%s" % (a_iface_name, a_fqdn)
1634 wleiden_cname["d-at-%s.%s" % (b_iface_name,sn(b_fqdn))] = "d-at-%s.%s" % (b_iface_name, b_fqdn)
1635 wleiden_cname["d2%s.%s" % (sn(b_fqdn),sn(a_fqdn))] = "d-at-%s.%s" % (a_iface_name, a_fqdn)
1636 wleiden_cname["d2%s.%s" % (sn(a_fqdn),sn(b_fqdn))] = "d-at-%s.%s" % (b_iface_name, b_fqdn)
1637
[8588]1638 else:
1639 pool_members = [k[1] for k in value]
1640 for item in value:
[9283]1641 (iface_name, fqdn, ip) = item
[10919]1642 wleiden_zone["2ring.%s" % (fqdn)].append((ip, True))
[8598]1643
1644 # Include static DNS entries
1645 # XXX: Should they override the autogenerated results?
1646 # XXX: Convert input to yaml more useable.
1647 # Format:
1648 ##; this is a comment
[13418]1649 ## roomburgh=Roomburgh1
1650 ## apkerk1.Vosko=172.17.176.8 ;this as well
[10642]1651 dns_list = yaml.load(open(os.path.join(NODE_DIR,'../dns/staticDNS.yaml'),'r'))
[9938]1652
1653 # Hack to allow special entries, for development
[10642]1654 wleiden_raw = {}
[9938]1655
[10642]1656 for line in dns_list:
[10660]1657 reverse = False
[10642]1658 k, items = line.items()[0]
[10660]1659 if type(items) == dict:
1660 if items.has_key('reverse'):
1661 reverse = items['reverse']
1662 items = items['a']
1663 else:
1664 items = items['cname']
1665 items = [items] if type(items) != list else items
[10642]1666 for item in items:
1667 if item.startswith('IN '):
1668 wleiden_raw[k] = item
1669 elif valid_addr(item):
[10660]1670 wleiden_zone[k].append((item, reverse))
[8598]1671 else:
[10642]1672 wleiden_cname[k] = item
[9283]1673
[10986]1674 # Hack to get dynamic pool listing
1675 def chunks(l, n):
1676 return [l[i:i+n] for i in range(0, len(l), n)]
1677
1678 ntp_servers = [x[0] for x in get_nameservers()]
1679 for id, chunk in enumerate(chunks(ntp_servers,(len(ntp_servers)/4))):
1680 for ntp_server in chunk:
1681 wleiden_zone['%i.pool.ntp' % id].append((ntp_server, False))
1682
[8598]1683 details = dict()
1684 # 24 updates a day allowed
1685 details['serial'] = time.strftime('%Y%m%d%H')
1686
[10264]1687 if external:
[13940]1688 dns_masters = ['ns1.vanderzwet.net', 'ns1.transip.net', 'ns2.transip.eu']
[10264]1689 else:
[13898]1690 dns_masters = ['druif.wleiden.net'] + ["%s.wleiden.net" % x[1] for x in get_nameservers(max_servers=3)]
[10264]1691
1692 details['master'] = dns_masters[0]
1693 details['ns_servers'] = '\n'.join(['\tNS\t%s.' % x for x in dns_masters])
1694
[8598]1695 dns_header = '''
1696$TTL 3h
[11725]1697%(zone)s. SOA %(master)s. beheer.lijst.wirelessleiden.nl. ( %(serial)s 15m 15m 1w 60s )
[8598]1698 ; Serial, Refresh, Retry, Expire, Neg. cache TTL
1699
[10264]1700%(ns_servers)s
[8598]1701 \n'''
1702
[9283]1703
[10264]1704 if not os.path.isdir(output_dir):
1705 os.makedirs(output_dir)
[8598]1706 details['zone'] = 'wleiden.net'
[9284]1707 f = open(os.path.join(output_dir,"db." + details['zone']), "w")
[8598]1708 f.write(dns_header % details)
1709
[10655]1710 for host,items in wleiden_zone.iteritems():
1711 for ip,reverse in items:
[10730]1712 if ip not in ['0.0.0.0']:
[13645]1713 f.write("%s.wleiden.net. IN A %s\n" % (host.lower(), ip))
[8588]1714 for source,dest in wleiden_cname.iteritems():
[10730]1715 dest = dest if dest.endswith('.') else dest + ".wleiden.net."
1716 f.write("%s.wleiden.net. IN CNAME %s\n" % (source.lower(), dest.lower()))
[9938]1717 for source, dest in wleiden_raw.iteritems():
1718 f.write("%s.wleiden.net. %s\n" % (source, dest))
[8588]1719 f.close()
[9283]1720
[8598]1721 # Create whole bunch of specific sub arpa zones. To keep it compliant
1722 for s in range(16,32):
1723 details['zone'] = '%i.172.in-addr.arpa' % s
[9284]1724 f = open(os.path.join(output_dir,"db." + details['zone']), "w")
[8598]1725 f.write(dns_header % details)
[8588]1726
[8598]1727 #XXX: Not effient, fix to proper data structure and do checks at other
1728 # stages
[10655]1729 for host,items in wleiden_zone.iteritems():
1730 for ip,reverse in items:
1731 if not reverse:
1732 continue
[10642]1733 if valid_addr(ip):
[10655]1734 if valid_addr(ip):
1735 if int(ip.split('.')[1]) == s:
1736 rev_ip = '.'.join(reversed(ip.split('.')))
1737 f.write("%s.in-addr.arpa. IN PTR %s.wleiden.net.\n" % (rev_ip.lower(), host.lower()))
[8598]1738 f.close()
[8588]1739
[8598]1740
[8259]1741def usage():
[10567]1742 print """Usage: %(prog)s <argument>
1743Argument:
[13328]1744\tcleanup = Cleanup all YAML files to specified format
[10567]1745\tstandalone [port] = Run configurator webserver [8000]
1746\tdns [outputdir] = Generate BIND compliant zone files in dns [./dns]
[11326]1747\tnagios-export [--heavy-load] = Generate basic nagios configuration file.
[9589]1748\tfull-export = Generate yaml export script for heatmap.
[10567]1749\tstatic [outputdir] = Generate all config files and store on disk
1750\t with format ./<outputdir>/%%NODE%%/%%FILE%% [./static]
[10872]1751\ttest <node> [<file>] = Receive output for certain node [all files].
1752\ttest-cgi <node> <file> = Receive output of CGI script [all files].
[10567]1753\tlist <status> <items> = List systems which have certain status
[13606]1754\tcreate network.kml = Create Network KML file for use in Google Earth
[10563]1755
[10567]1756Arguments:
1757\t<node> = NodeName (example: HybridRick)
1758\t<file> = %(files)s
1759\t<status> = all|up|down|planned
1760\t<items> = systems|nodes|proxies
1761
[10563]1762NOTE FOR DEVELOPERS; you can test your changes like this:
1763 BEFORE any changes in this code:
1764 $ ./gformat.py static /tmp/pre
1765 AFTER the changes:
1766 $ ./gformat.py static /tmp/post
1767 VIEW differences and VERIFY all are OK:
[10564]1768 $ diff -urI 'Generated' -r /tmp/pre /tmp/post
[10567]1769""" % { 'prog' : sys.argv[0], 'files' : '|'.join(files) }
[8259]1770 exit(0)
1771
1772
[11426]1773def is_text_request(environ=os.environ):
[10107]1774 """ Find out whether we are calling from the CLI or any text based CLI utility """
[13936]1775 if 'CONTENT_TYPE' in os.environ and os.environ['CONTENT_TYPE'] == 'text/plain':
[10107]1776 return True
[8259]1777
[13936]1778 if 'HTTP_USER_AGENT' in environ:
1779 return any([os.environ['HTTP_USER_AGENT'].lower().startswith(x) for x in ['curl', 'fetch', 'wget']])
1780 else:
1781 return False
1782
1783
[10547]1784def switchFormat(setting):
1785 if setting:
1786 return "YES"
1787 else:
1788 return "NO"
1789
[10885]1790def rlinput(prompt, prefill=''):
1791 import readline
1792 readline.set_startup_hook(lambda: readline.insert_text(prefill))
1793 try:
1794 return raw_input(prompt)
1795 finally:
1796 readline.set_startup_hook()
1797
1798def fix_conflict(left, right, default='i'):
1799 while True:
1800 print "## %-30s | %-30s" % (left, right)
1801 c = raw_input("## Solve Conflict (h for help) <l|r|e|i|> [%s]: " % default)
1802 if not c:
1803 c = default
1804
1805 if c in ['l','1']:
1806 return left
1807 elif c in ['r','2']:
1808 return right
1809 elif c in ['e', '3']:
1810 return rlinput("Edit: ", "%30s | %30s" % (left, right))
1811 elif c in ['i', '4']:
1812 return None
1813 else:
1814 print "#ERROR: '%s' is invalid input (left, right, edit or ignore)!" % c
1815
[11427]1816
1817
1818def print_cgi_response(response_headers, output):
1819 for header in response_headers:
1820 print "%s: %s" % header
[11444]1821 print
[11427]1822 print output
1823
1824
1825
[8267]1826def main():
1827 """Hard working sub"""
1828 # Allow easy hacking using the CLI
[13935]1829 if not os.environ.has_key('REQUEST_URI'):
[8267]1830 if len(sys.argv) < 2:
1831 usage()
[9283]1832
[8267]1833 if sys.argv[1] == "standalone":
1834 import SocketServer
1835 import CGIHTTPServer
[10105]1836 # Hop to the right working directory.
1837 os.chdir(os.path.dirname(__file__))
[8267]1838 try:
1839 PORT = int(sys.argv[2])
1840 except (IndexError,ValueError):
1841 PORT = 8000
[9283]1842
[8267]1843 class MyCGIHTTPRequestHandler(CGIHTTPServer.CGIHTTPRequestHandler):
1844 """ Serve this CGI from the root of the webserver """
1845 def is_cgi(self):
1846 if "favicon" in self.path:
1847 return False
[9283]1848
[10364]1849 self.cgi_info = (os.path.basename(__file__), self.path)
[8267]1850 self.path = ''
1851 return True
1852 handler = MyCGIHTTPRequestHandler
[9807]1853 SocketServer.TCPServer.allow_reuse_address = True
[8267]1854 httpd = SocketServer.TCPServer(("", PORT), handler)
1855 httpd.server_name = 'localhost'
1856 httpd.server_port = PORT
[9283]1857
[9728]1858 logger.info("serving at port %s", PORT)
[8860]1859 try:
1860 httpd.serve_forever()
1861 except KeyboardInterrupt:
1862 httpd.shutdown()
[9728]1863 logger.info("All done goodbye")
[8267]1864 elif sys.argv[1] == "test":
[10872]1865 # Basic argument validation
1866 try:
1867 node = sys.argv[2]
1868 except IndexError:
1869 print "Invalid argument"
1870 exit(1)
1871 except IOError as e:
1872 print e
1873 exit(1)
[13403]1874
1875 datadump = get_yaml(node)
[10872]1876
1877
1878 # Get files to generate
1879 gen_files = sys.argv[3:] if len(sys.argv) > 3 else files
1880
1881 # Actual config generation
1882 for config in gen_files:
1883 logger.info("## Generating %s %s", node, config)
1884 print generate_config(node, config, datadump)
1885 elif sys.argv[1] == "test-cgi":
[13935]1886 os.environ['REQUEST_URI'] = "/".join(['config'] + sys.argv[2:])
[8267]1887 os.environ['SCRIPT_NAME'] = __file__
[11427]1888 response_headers, output = process_cgi_request()
1889 print_cgi_response(response_headers, output)
[8296]1890 elif sys.argv[1] == "static":
[13937]1891 generate_static(sys.argv[2] if len(sys.argv) > 2 else "./static")
[9514]1892 elif sys.argv[1] == "wind-export":
1893 items = dict()
1894 for node in get_hostlist():
1895 datadump = get_yaml(node)
1896 sql = """INSERT IGNORE INTO nodes (name, name_ns, longitude, latitude)
1897 VALUES ('%(nodename)s', '%(nodename)s', %(latitude)s, %(longitude)s);""" % datadump;
1898 sql = """INSERT IGNORE INTO users_nodes (user_id, node_id, owner)
1899 VALUES (
1900 (SELECT id FROM users WHERE username = 'rvdzwet'),
1901 (SELECT id FROM nodes WHERE name = '%(nodename)s'),
1902 'Y');""" % datadump
1903 #for config in files:
1904 # items['config'] = config
1905 # print "## Generating %(node)s %(config)s" % items
1906 # f = open("%(wdir)s/%(config)s" % items, "w")
1907 # f.write(generate_config(node, config, datadump))
1908 # f.close()
1909 for node in get_hostlist():
1910 datadump = get_yaml(node)
1911 for iface_key in sorted([elem for elem in datadump.keys() if elem.startswith('iface_')]):
1912 ifacedump = datadump[iface_key]
1913 if ifacedump.has_key('mode') and ifacedump['mode'] == 'ap-wds':
1914 ifacedump['nodename'] = datadump['nodename']
1915 if not ifacedump.has_key('channel') or not ifacedump['channel']:
1916 ifacedump['channel'] = 0
1917 sql = """INSERT INTO links (node_id, type, ssid, protocol, channel, status)
1918 VALUES ((SELECT id FROM nodes WHERE name = '%(nodename)s'), 'ap',
1919 '%(ssid)s', 'IEEE 802.11b', %(channel)s, 'active');""" % ifacedump
[11326]1920 elif sys.argv[1] == "nagios-export":
1921 try:
1922 heavy_load = (sys.argv[2] == "--heavy-load")
1923 except IndexError:
1924 heavy_load = False
1925
1926 hostgroup_details = {
1927 'wleiden' : 'Stichting Wireless Leiden - FreeBSD Nodes',
1928 'wzoeterwoude' : 'Stichting Wireless Leiden - Afdeling Zoeterwoude - Free-WiFi Project',
1929 'walphen' : 'Stichting Wireless Alphen',
[13274]1930 'westeinder' : 'Westeinder Plassen',
[11326]1931 }
1932
[13274]1933 # Convert IP to Host
1934 ip2host = {'root' : 'root'}
1935 for host in get_hostlist():
1936 datadump = get_yaml(host)
1937 ip2host[datadump['masterip']] = datadump['autogen_fqdn']
[13328]1938 for iface in get_interface_keys(datadump):
[13618]1939 if datadump[iface].has_key('autogen_gateway'):
1940 ip2host[datadump[iface]['autogen_gateway']] = datadump['autogen_fqdn']
[13274]1941
1942 # Find dependency tree based on output of lvrouted.mytree of nearest node
[13276]1943 parents = defaultdict(list)
[13274]1944 stack = ['root']
1945 prev_depth = 0
1946 for line in open('lvrouted.mytree').readlines():
1947 depth = line.count('\t')
1948 ip = line.strip().split()[0]
1949
1950 if prev_depth < depth:
[13276]1951 try:
1952 parents[ip2host[ip]].append(ip2host[stack[-1]])
1953 except KeyError as e:
1954 print >> stderr, "# Unable to find %s in configuration files" % e.args[0]
[13274]1955 stack.append(ip)
1956 elif prev_depth > depth:
1957 stack = stack[:(depth - prev_depth)]
[13276]1958 elif prev_depth == depth:
1959 try:
1960 parents[ip2host[ip]].append(ip2host[stack[-1]])
1961 except KeyError as e:
1962 print >> stderr, "# Unable to find %s in configuration files" % e.args[0]
[13274]1963
[13276]1964
[13274]1965 prev_depth = depth
1966 # Observe that some nodes has themself as parent or multiple parents
1967 # for now take only the first parent, other behaviour is yet to be explained
1968
1969
1970
[11326]1971 params = {
[12787]1972 'check_interval' : 5 if heavy_load else 120,
1973 'retry_interval' : 1 if heavy_load else 10,
1974 'max_check_attempts' : 10 if heavy_load else 6,
[13263]1975 'notification_interval': 120 if heavy_load else 240,
[11326]1976 }
1977
1978 print '''\
1979define host {
1980 name wleiden-node ; Default Node Template
1981 use generic-host ; Use the standard template as initial starting point
1982 check_period 24x7 ; By default, FreeBSD hosts are checked round the clock
1983 check_interval %(check_interval)s ; Actively check the host every 5 minutes
1984 retry_interval %(retry_interval)s ; Schedule host check retries at 1 minute intervals
[13263]1985 notification_interval %(notification_interval)s
[11326]1986 max_check_attempts %(max_check_attempts)s ; Check each FreeBSD host 10 times (max)
[12482]1987 check_command check-host-alive ; Default command to check FreeBSD hosts
[11326]1988 register 0 ; DONT REGISTER THIS DEFINITION - ITS NOT A REAL HOST, JUST A TEMPLATE!
1989}
1990
1991define service {
1992 name wleiden-service ; Default Service Template
1993 use generic-service ; Use the standard template as initial starting point
1994 check_period 24x7 ; By default, FreeBSD hosts are checked round the clock
1995 check_interval %(check_interval)s ; Actively check the host every 5 minutes
1996 retry_interval %(retry_interval)s ; Schedule host check retries at 1 minute intervals
[13263]1997 notification_interval %(notification_interval)s
[11326]1998 max_check_attempts %(max_check_attempts)s ; Check each FreeBSD host 10 times (max)
1999 register 0 ; DONT REGISTER THIS DEFINITION - ITS NOT A REAL HOST, JUST A TEMPLATE!
2000}
2001
2002# Please make sure to install:
[13264]2003# make -C /usr/ports/net-mgmt/nagios-check_netsnmp install clean
2004#
2005# Recompile net-mgmt/nagios-plugins to support check_snmp
2006# make -C /usr/ports/net-mgmt/nagios-plugins
[11326]2007#
[13264]2008# Install net/bind-tools to allow v2/check_dns_wl to work:
2009# pkg install bind-tools
2010#
[11326]2011define command{
[13264]2012 command_name check_snmp_disk
2013 command_line $USER1$/check_snmp_disk -H $HOSTADDRESS$ -C public
[11326]2014}
2015
2016define command{
2017 command_name check_netsnmp_load
[13264]2018 command_line $USER1$/check_snmp_load.pl -H $HOSTADDRESS$ -C public -w 80 -c 90
[11326]2019}
2020
2021define command{
2022 command_name check_netsnmp_proc
[13264]2023 command_line $USER1$/check_snmp_proc -H $HOSTADDRESS$ -C public
[11326]2024}
2025
[12787]2026define command{
2027 command_name check_by_ssh
2028 command_line $USER1$/check_by_ssh -H $HOSTADDRESS$ -p $ARG1$ -C "$ARG2$ $ARG3$ $ARG4$ $ARG5$ $ARG6$"
2029}
2030
2031define command{
2032 command_name check_dns_wl
2033 command_line $USER1$/v2/check_dns_wl $HOSTADDRESS$ $ARG1$
2034}
2035
[13264]2036define command{
2037 command_name check_snmp_uptime
2038 command_line $USER1$/check_snmp -H $HOSTADDRESS$ -C public -o .1.3.6.1.2.1.1.3.0
2039}
[12787]2040
[13264]2041
[11326]2042# TDB: dhcp leases
2043# /usr/local/libexec/nagios/check_netsnmp -H 192.168.178.47 --oid 1 exec
2044
2045# TDB: internet status
2046# /usr/local/libexec/nagios/check_netsnmp -H 192.168.178.47 --oid 1 file
2047
2048# TDB: Advanced local passive checks
2049# /usr/local/libexec/nagios/check_by_ssh
2050''' % params
2051
2052 print '''\
2053# Service Group, not displayed by default
2054define hostgroup {
2055 hostgroup_name srv_hybrid
2056 alias All Hybrid Nodes
2057 register 0
2058}
2059
2060define service {
2061 use wleiden-service
2062 hostgroup_name srv_hybrid
2063 service_description SSH
2064 check_command check_ssh
2065}
2066
2067define service {
[13278]2068 use wleiden-service,service-pnp
[11326]2069 hostgroup_name srv_hybrid
2070 service_description HTTP
2071 check_command check_http
2072}
2073
[12787]2074define service {
2075 use wleiden-service
2076 hostgroup_name srv_hybrid
2077 service_description DNS
2078 check_command check_dns_wl!"www.wirelessleiden.nl"
2079}
[11326]2080'''
2081
2082 if heavy_load:
2083 print '''\
2084define service {
2085 use wleiden-service
2086 hostgroup_name srv_hybrid
[13264]2087 service_description UPTIME
2088 check_command check_snmp_uptime
[11326]2089}
2090
[13263]2091#define service {
2092# use wleiden-service
2093# hostgroup_name srv_hybrid
2094# service_description NTP
2095# check_command check_ntp_peer
2096#}
[11326]2097
2098define service {
2099 use wleiden-service
2100 hostgroup_name srv_hybrid
2101 service_description LOAD
2102 check_command check_netsnmp_load
2103}
2104
2105define service {
2106 use wleiden-service
2107 hostgroup_name srv_hybrid
2108 service_description PROC
2109 check_command check_netsnmp_proc
2110}
2111
2112define service {
2113 use wleiden-service
2114 hostgroup_name srv_hybrid
2115 service_description DISK
[13264]2116 check_command check_snmp_disk
[11326]2117}
2118'''
2119 for node in get_hostlist():
2120 datadump = get_yaml(node)
2121 if not datadump['status'] == 'up':
2122 continue
2123 if not hostgroup_details.has_key(datadump['monitoring_group']):
2124 hostgroup_details[datadump['monitoring_group']] = datadump['monitoring_group']
2125 print '''\
2126define host {
[13278]2127 use wleiden-node,host-pnp
[13263]2128 contact_groups admins
[11326]2129 host_name %(autogen_fqdn)s
2130 address %(masterip)s
[13274]2131 hostgroups srv_hybrid,%(monitoring_group)s\
2132''' % datadump
[13277]2133 if (len(parents[datadump['autogen_fqdn']]) > 0) and parents[datadump['autogen_fqdn']][0] != 'root':
[13274]2134 print '''\
[13276]2135 parents %(parents)s\
2136''' % { 'parents' : parents[datadump['autogen_fqdn']][0] }
[13274]2137 print '''\
[11326]2138}
[13274]2139'''
[11326]2140
[13274]2141
[11326]2142 for name,alias in hostgroup_details.iteritems():
2143 print '''\
2144define hostgroup {
2145 hostgroup_name %s
2146 alias %s
2147} ''' % (name, alias)
2148
2149
[9589]2150 elif sys.argv[1] == "full-export":
2151 hosts = {}
2152 for node in get_hostlist():
2153 datadump = get_yaml(node)
2154 hosts[datadump['nodename']] = datadump
2155 print yaml.dump(hosts)
2156
[8584]2157 elif sys.argv[1] == "dns":
[10264]2158 make_dns(sys.argv[2] if len(sys.argv) > 2 else 'dns', 'external' in sys.argv)
[9283]2159 elif sys.argv[1] == "cleanup":
[8588]2160 # First generate all datadumps
2161 datadumps = dict()
[10729]2162 ssid_to_node = dict()
[8588]2163 for host in get_hostlist():
[9728]2164 logger.info("# Processing: %s", host)
[10436]2165 # Set some boring default values
2166 datadump = { 'board' : 'UNKNOWN' }
2167 datadump.update(get_yaml(host))
[13405]2168 datadumps[datadump['nodename']] = datadump
[9283]2169
[13327]2170 (poel, errors) = make_relations()
[10729]2171 print "\n".join(["# WARNING: %s" % x for x in errors])
[10455]2172
[10156]2173 for host,datadump in datadumps.iteritems():
[10881]2174 try:
2175 # Convert all yes and no to boolean values
2176 def fix_boolean(dump):
2177 for key in dump.keys():
2178 if type(dump[key]) == dict:
2179 dump[key] = fix_boolean(dump[key])
2180 elif str(dump[key]).lower() in ["yes", "true"]:
2181 dump[key] = True
2182 elif str(dump[key]).lower() in ["no", "false"]:
2183 # Compass richting no (Noord Oost) is valid input
2184 if key != "compass": dump[key] = False
2185 return dump
2186 datadump = fix_boolean(datadump)
[10455]2187
[13325]2188 if 'rdnap_x' in datadump and 'rdnap_y' in datadump:
[13843]2189 if not 'latitude' in datadump and not 'longitude' in datadump:
2190 datadump['latitude'], datadump['longitude'] = map(lambda x: "%.5f" % x, rd2etrs(datadump['rdnap_x'], datadump['rdnap_y']))
[13325]2191 elif 'latitude' in datadump and 'longitude' in datadump:
[13843]2192 if not 'rdnap_x' in datadump and not 'rdnap_y' in datadump:
2193 datadump['rdnap_x'], datadump['rdnap_y'] = etrs2rd(datadump['latitude'], datadump['longitude'])
2194 # TODO: Compare outcome of both coordinate systems and validate against each-other
[10400]2195
[10881]2196 if datadump['nodename'].startswith('Proxy'):
2197 datadump['nodename'] = datadump['nodename'].lower()
[10319]2198
[13328]2199 for iface_key in get_interface_keys(datadump):
[10889]2200 try:
2201 # All our normal wireless cards are normal APs now
2202 if datadump[iface_key]['type'] in ['11a', '11b', '11g', 'wireless']:
2203 datadump[iface_key]['mode'] = 'ap'
2204 # Wireless Leiden SSID have an consistent lowercase/uppercase
2205 if datadump[iface_key].has_key('ssid'):
2206 ssid = datadump[iface_key]['ssid']
2207 prefix = 'ap-WirelessLeiden-'
2208 if ssid.lower().startswith(prefix.lower()):
2209 datadump[iface_key]['ssid'] = prefix + ssid[len(prefix)].upper() + ssid[len(prefix) + 1:]
2210 if datadump[iface_key].has_key('ns_ip') and not datadump[iface_key].has_key('mode'):
2211 datadump[iface_key]['mode'] = 'autogen-FIXME'
2212 if not datadump[iface_key].has_key('comment'):
2213 datadump[iface_key]['comment'] = 'autogen-FIXME'
[10882]2214
[11732]2215 if datadump[iface_key].has_key('ns_mac'):
2216 datadump[iface_key]['ns_mac'] = datadump[iface_key]['ns_mac'].lower()
2217
[10889]2218 if datadump[iface_key]['comment'].startswith('autogen-') and datadump[iface_key].has_key('comment'):
2219 datadump[iface_key] = datadump[iface_key]['desc']
[10882]2220
[11738]2221 # We are not using 802.11b anymore. OFDM is preferred over DSSS
2222 # due to better collision avoidance.
2223 if datadump[iface_key]['type'] == '11b':
2224 datadump[iface_key]['type'] = '11g'
2225
2226 # Setting 802.11g channels to de-facto standards, to avoid
2227 # un-detected sharing with other overlapping channels
2228 #
2229 # Technically we could also use channel 13 in NL, but this is not
2230 # recommended as foreign devices might not be able to select this
2231 # channel. Secondly using 1,5,9,13 instead is going to clash with
2232 # the de-facto usage of 1,6,11.
2233 #
2234 # See: https://en.wikipedia.org/wiki/List_of_WLAN_channels
2235 channels_at_2400Mhz = (1,6,11)
2236 if datadump[iface_key]['type'] == '11g' and datadump[iface_key].has_key('channel'):
2237 datadump[iface_key]['channel'] = int(datadump[iface_key]['channel'])
2238 if datadump[iface_key]['channel'] not in channels_at_2400Mhz:
2239 datadump[iface_key]['channel'] = random.choice(channels_at_2400Mhz)
2240
[11555]2241 # Mandatory interface keys
2242 if not datadump[iface_key].has_key('status'):
2243 datadump[iface_key]['status'] = 'planned'
2244
[10889]2245 x = datadump[iface_key]['comment']
2246 datadump[iface_key]['comment'] = x[0].upper() + x[1:]
[10884]2247
[12478]2248 # Fixing bridge_type if none is found
2249 if datadump[iface_key].get('extra_type', '') == 'eth2wifibridge':
2250 if not 'bridge_type' in datadump[iface_key]:
2251 datadump[iface_key]['bridge_type'] = 'NanoStation M5'
2252
2253 # Making sure description works
[10889]2254 if datadump[iface_key].has_key('desc'):
2255 if datadump[iface_key]['comment'].lower() == datadump[iface_key]['desc'].lower():
[10885]2256 del datadump[iface_key]['desc']
[10889]2257 else:
2258 print "# ERROR: At %s - %s" % (datadump['nodename'], iface_key)
2259 response = fix_conflict(datadump[iface_key]['comment'], datadump[iface_key]['desc'])
2260 if response:
2261 datadump[iface_key]['comment'] = response
2262 del datadump[iface_key]['desc']
[10882]2263
[10889]2264 # Check DHCP configuration
2265 dhcp_type(datadump[iface_key])
2266
2267 # Set the compass value based on the angle between the poels
[13843]2268 if 'ns_ip' in datadump[iface_key] and 'ip' in datadump[iface_key] and not 'compass' in datadump[iface_key]:
[10889]2269 my_pool = poel[network(datadump[iface_key]['ip'])]
2270 remote_hosts = list(set([x[0] for x in my_pool]) - set([host]))
2271 if remote_hosts:
2272 compass_target = remote_hosts[0]
2273 datadump[iface_key]['compass'] = cd_between_hosts(host, compass_target, datadumps)
[13843]2274 # TODO: Compass wanted and actual direction might differ
[12475]2275
2276 # Monitoring Group default
2277 if not 'monitoring_group' in datadump:
2278 datadump['monitoring_group'] = 'wleiden'
2279
[13985]2280 except Exception as exc:
2281 exc.args = ("# Error while processing interface %s" % iface_key,) + exc.args
[10889]2282 raise
[10881]2283 store_yaml(datadump)
[13985]2284 except Exception as exc:
2285 exc.args = ("# Error while processing %s" % host,) + exc.args
[10881]2286 raise
[9971]2287 elif sys.argv[1] == "list":
[10611]2288 use_fqdn = False
[13279]2289 if len(sys.argv) < 4:
[10567]2290 usage()
[13279]2291 if not sys.argv[2] in ["up", "down", "planned", "all"]:
[9971]2292 usage()
[13279]2293 if not sys.argv[3] in ["nodes","proxies","systems"]:
2294 usage()
2295
[10611]2296 if len(sys.argv) > 4:
2297 if sys.argv[4] == "fqdn":
2298 use_fqdn = True
2299 else:
2300 usage()
2301
[13279]2302 for system in get_hostlist():
[9971]2303 datadump = get_yaml(system)
[13279]2304 if sys.argv[3] == 'proxies' and not datadump['service_proxy_ileiden']:
2305 continue
[10611]2306
2307 output = datadump['autogen_fqdn'] if use_fqdn else system
[10567]2308 if sys.argv[2] == "all":
[10611]2309 print output
[10567]2310 elif datadump['status'] == sys.argv[2]:
[10611]2311 print output
[10378]2312 elif sys.argv[1] == "create":
2313 if sys.argv[2] == "network.kml":
2314 print make_network_kml.make_graph()
[10998]2315 elif sys.argv[2] == "host-ips.txt":
2316 for system in get_hostlist():
2317 datadump = get_yaml(system)
2318 ips = [datadump['masterip']]
[13328]2319 for ifkey in get_interface_keys(datadump):
[10998]2320 ips.append(datadump[ifkey]['ip'].split('/')[0])
2321 print system, ' '.join(ips)
[10999]2322 elif sys.argv[2] == "host-pos.txt":
2323 for system in get_hostlist():
2324 datadump = get_yaml(system)
2325 print system, datadump['rdnap_x'], datadump['rdnap_y']
[12233]2326 elif sys.argv[2] == 'ssh_config':
2327 print '''
2328Host *.wleiden.net
2329 User root
2330
2331Host 172.16.*.*
2332 User root
2333'''
2334 for system in get_hostlist():
2335 datadump = get_yaml(system)
2336 print '''\
2337Host %s
2338 User root
2339
2340Host %s
2341 User root
2342
2343Host %s
2344 User root
2345
2346Host %s
2347 User root
2348''' % (system, system.lower(), datadump['nodename'], datadump['nodename'].lower())
[10378]2349 else:
[10998]2350 usage()
2351 else:
[9283]2352 usage()
2353 else:
[10070]2354 # Do not enable debugging for config requests as it highly clutters the output
2355 if not is_text_request():
2356 cgitb.enable()
[11427]2357 response_headers, output = process_cgi_request()
2358 print_cgi_response(response_headers, output)
[9283]2359
2360if __name__ == "__main__":
2361 main()
Note: See TracBrowser for help on using the repository browser.