source: genesis/tools/gformat.py@ 13703

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

Implement named replacement for FreeBSD 11.

Unbound via ports, configured to listen to only specific interfaces.
For security purposes do not listen to outside world.

Depending on it's location, making sure the forwarders are internal
or external.

Note: Dropping support for autoritive nameserver, this feature is not
required anymore since our network is running stable now.

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