source: genesis/tools/gformat.py@ 13327

Last change on this file since 13327 was 13327, checked in by rick, 9 years ago

Get rid of the formatting quircks for the tables

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