source: genesis/tools/gformat.py@ 13843

Last change on this file since 13843 was 13843, checked in by rick, 8 years ago

Fix generation & cleanup of config files

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