source: genesis/tools/gformat.py@ 13735

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

autogen_ifname is ambigious since introduction of vlans.

dhcpd grouping should not group vlans since they are seperate interfaces (as
DHCP is concerned), where-as normal aliases should be grouped.

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