source: genesis/tools/gformat.py@ 13599

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

Typo ether and inet have a different syntax

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