source: genesis/tools/gformat.py@ 13762

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

Instead of 32 spaces, use 2 spares, since unbound does not seems to like this setup

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