source: genesis/tools/gformat.py@ 13398

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

defaultrouter= should also be used for iLeiden proxies.

  • Property svn:executable set to *
  • Property svn:keywords set to Id
File size: 72.4 KB
Line 
1#!/usr/bin/env python
2#
3# vim:ts=2:et:sw=2:ai
4# Wireless Leiden configuration generator, based on yaml files'
5#
6# XXX: This should be rewritten to make use of the ipaddr.py library.
7#
8# Sample apache configuration (mind the AcceptPathInfo!)
9# ScriptAlias /wleiden/config /usr/local/www/genesis/tools/gformat.py
10# <Directory /usr/local/www/genesis>
11# Allow from all
12# AcceptPathInfo On
13# </Directory>
14#
15# MUCH FASTER WILL IT BE with mod_wsgi, due to caches and avoiding loading all
16# the heavy template lifting all the time.
17#
18# WSGIDaemonProcess gformat threads=25
19# WSGISocketPrefix run/wsgi
20#
21# <Directory /var/www/cgi-bin>
22# WSGIProcessGroup gformat
23# </Directory>
24# WSGIScriptAlias /hello /var/www/cgi-bin/genesis/tools/gformat.py
25#
26# Package dependencies list:
27# yum install python-yaml pyproj proj-epsg python-jinja2
28#
29# Rick van der Zwet <info@rickvanderzwet.nl>
30#
31
32# Hack to make the script directory is also threated as a module search path.
33import sys
34import os
35sys.path.append(os.path.dirname(__file__))
36
37SVN = filter(os.path.isfile, ('/usr/local/bin/svn', '/usr/bin/svn'))[0]
38
39import argparse
40import cgi
41import cgitb
42import copy
43import glob
44import make_network_kml
45import math
46import pyproj
47import random
48import re
49import socket
50import string
51import subprocess
52import textwrap
53import time
54import urlparse
55
56from pprint import pprint
57from collections import defaultdict
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 13398 2015-09-18 13:33:18Z rick $'
100
101files = [
102 'authorized_keys',
103 'dnsmasq.conf',
104 'dhcpd.conf',
105 'rc.conf.local',
106 'resolv.conf',
107 'motd',
108 'ntp.conf',
109 'pf.hybrid.conf.local',
110 'wleiden.yaml',
111 ]
112
113# Global variables uses
114OK = 10
115DOWN = 20
116UNKNOWN = 90
117
118
119ileiden_proxies = []
120normal_proxies = []
121datadump_cache = {}
122interface_list_cache = {}
123rc_conf_local_cache = {}
124nameservers_cache = []
125relations_cache = None
126def clear_cache():
127 ''' Poor mans cache implementation '''
128 global datadump_cache, interface_list_cache, rc_conf_local_cache, ileiden_proxies, normal_proxies, nameservers_cache
129 datadump_cache = {}
130 interface_list_cache = {}
131 rc_conf_local_cache = {}
132 ileiden_proxies = []
133 normal_proxies = []
134 nameservers_cache = []
135 relations_cache = None
136
137NO_DHCP = 0
138DHCP_CLIENT = 10
139DHCP_SERVER = 20
140def dhcp_type(item):
141 if not item.has_key('dhcp'):
142 return NO_DHCP
143 elif not item['dhcp']:
144 return NO_DHCP
145 elif item['dhcp'].lower() == 'client':
146 return DHCP_CLIENT
147 else:
148 # Validation Checks
149 begin,end = map(int,item['dhcp'].split('-'))
150 if begin >= end:
151 raise ValueError("DHCP Start >= DHCP End")
152 return DHCP_SERVER
153
154def etrs2rd(lat, lon):
155 p1 = pyproj.Proj(proj='latlon',datum='WGS84')
156 p2 = pyproj.Proj(init='EPSG:28992')
157 RDx, RDy = pyproj.transform(p1,p2,lon, lat)
158 return (RDx, RDy)
159
160def rd2etrs(RDx, RDy):
161 p1 = pyproj.Proj(init='EPSG:28992')
162 p2 = pyproj.Proj(proj='latlon',datum='WGS84')
163 lon, lat = pyproj.transform(p1,p2, RDx, RDy)
164 return (lat, lon)
165
166def get_yaml(item,add_version_info=True):
167 try:
168 """ Get configuration yaml for 'item'"""
169 if datadump_cache.has_key(item):
170 return datadump_cache[item].copy()
171
172 gfile = os.path.join(NODE_DIR,item,'wleiden.yaml')
173
174 # Default values
175 datadump = {
176 'autogen_revision' : 'NOTFOUND',
177 'autogen_gfile' : gfile,
178 'service_proxy_ileiden' : False,
179 }
180 f = open(gfile, 'r')
181 datadump.update(yaml.load(f,Loader=Loader))
182 if datadump['nodetype'] == 'Hybrid':
183 # Some values are defined implicitly
184 if datadump.has_key('rdr_rules') and datadump['rdr_rules'] and not datadump.has_key('service_incoming_rdr'):
185 datadump['service_incoming_rdr'] = True
186 # Use some boring defaults
187 defaults = {
188 'service_proxy_normal' : False,
189 'service_accesspoint' : True,
190 'service_incoming_rdr' : False,
191 'service_concentrator' : False,
192 'monitoring_group' : 'wleiden',
193 }
194 for (key,value) in defaults.iteritems():
195 if not datadump.has_key(key):
196 datadump[key] = value
197 f.close()
198
199 # Sometimes getting version information is useless or harmfull, like in the pre-commit hooks
200 if add_version_info:
201 p = subprocess.Popen([SVN, 'info', datadump['autogen_gfile']], stderr=subprocess.STDOUT, stdout=subprocess.PIPE)
202 lines = p.communicate()[0].split('\n')
203 if p.returncode == 0:
204 for line in lines:
205 if line:
206 (key, value) = line.split(': ')
207 datadump["autogen_" + key.lower().replace(' ','_')] = value
208
209 # Preformat certain needed variables for formatting and push those into special object
210 datadump['autogen_iface_keys'] = get_interface_keys(datadump)
211
212 wlan_count=0
213 try:
214 for key in get_interface_keys(datadump, True):
215 datadump[key]['autogen_ifbase'] = key.split('_')[1]
216 datadump[key]['autogen_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 get_interface_keys(datadump):
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 get_interface_keys(datadump):
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 get_interface_keys(datadump):
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 get_interface_keys(datadump):
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 get_interface_keys(datadump):
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 datadump['autogen_attached_devices'] = [x[2] for x in get_attached_devices(datadump)]
718 datadump['autogen_neighbours'] = [x[1] for x in get_neighbours(datadump)]
719
720 output = generate_header(datadump, "#");
721 output += render_template(datadump, """\
722hostname='{{ autogen_fqdn }}'
723location='{{ location }}'
724nodetype="{{ nodetype }}"
725
726#
727# Configured listings
728#
729captive_portal_whitelist=""
730{% if nodetype == "Proxy" %}
731#
732# Proxy Configuration
733#
734{% if gateway and service_proxy_ileiden -%}
735defaultrouter="{{ gateway }}"
736{% else -%}
737#defaultrouter="NOTSET"
738{% endif -%}
739internalif="{{ internalif }}"
740ileiden_enable="{{ autogen_ileiden_enable }}"
741gateway_enable="{{ autogen_ileiden_enable }}"
742pf_enable="yes"
743pf_rules="/etc/pf.conf"
744{% if autogen_ileiden_enable -%}
745pf_flags="-D ext_if={{ externalif }} -D int_if={{ internalif }} -D publicnat={80,443}"
746lvrouted_enable="{{ autogen_ileiden_enable }}"
747lvrouted_flags="-u -s s00p3rs3kr3t -m 28"
748{% else -%}
749pf_flags="-D ext_if={{ externalif }} -D int_if={{ internalif }} -D publicnat={0}"
750{% endif -%}
751{% if internalroute -%}
752static_routes="wleiden"
753route_wleiden="-net 172.16.0.0/12 {{ internalroute }}"
754{% endif -%}
755
756{% elif nodetype == "Hybrid" %}
757 #
758 # Hybrid Configuration
759 #
760 list_ileiden_proxies="
761 {% for item in autogen_ileiden_proxies -%}
762 {{ "%-16s"|format(item.masterip) }} # {{ item.autogen_realname }}
763 {% endfor -%}
764 "
765 list_normal_proxies="
766 {% for item in autogen_normal_proxies -%}
767 {{ "%-16s"|format(item.masterip) }} # {{ item.autogen_realname }}
768 {% endfor -%}
769 "
770
771 captive_portal_interfaces="{{ autogen_dhcp_interfaces|join(',')|default('none', true) }}"
772 externalif="{{ externalif|default('vr0', true) }}"
773 masterip="{{ masterip }}"
774
775 {% if gateway and service_proxy_ileiden %}
776 defaultrouter="{{ gateway }}"
777 {% else %}
778 #defaultrouter="NOTSET"
779 {% endif %}
780
781 #
782 # Defined services
783 #
784 service_proxy_ileiden="{{ service_proxy_ileiden|yesorno }}"
785 service_proxy_normal="{{ service_proxy_normal|yesorno }}"
786 service_accesspoint="{{ service_accesspoint|yesorno }}"
787 service_incoming_rdr="{{ service_incoming_rdr|yesorno }}"
788 service_concentrator="{{ service_concentrator|yesorno }}"
789
790 {% if service_proxy_ileiden %}
791 pf_rules="/etc/pf.hybrid.conf"
792 {% if service_concentrator %}
793 pf_flags="-D ext_if=$externalif -D ext_if_net=$externalif:network -D inet_if=tun0 -D inet_ip='(tun0)' -D masterip=$masterip"
794 {% else %}
795 pf_flags="-D ext_if=$externalif -D ext_if_net=$externalif:network -D inet_if=$externalif -D inet_ip='($externalif:0)' -D masterip=$masterip"
796 {% endif %}
797 pf_flags="$pf_flags -D publicnat=80,443"
798 lvrouted_flags="$lvrouted_flags -g"
799 {% elif service_proxy_normal or service_incoming_rdr %}
800 pf_rules="/etc/pf.hybrid.conf"
801 pf_flags="-D ext_if=$externalif -D ext_if_net=$externalif:network -D masterip=$masterip"
802 pf_flags="$pf_flags -D publicnat=0"
803 lvrouted_flags="$lvrouted_flags -z `make_list "$list_ileiden_proxies" ","`"
804 named_setfib="1"
805 tinyproxy_setfib="1"
806 dnsmasq_setfib="1"
807 sshd_setfib="1"
808 {% else %}
809 named_auto_forward_only="YES"
810 pf_rules="/etc/pf.node.conf"
811 pf_flags=""
812 lvrouted_flags="$lvrouted_flags -z `make_list "$list_ileiden_proxies" ","`"
813 {% endif %}
814 {% if service_concentrator %}
815 # Do mind installing certificates is NOT done automatically for security reasons
816 openvpn_enable="YES"
817 openvpn_configfile="/usr/local/etc/openvpn/client.conf"
818 {% endif %}
819
820 {% if service_proxy_normal %}
821 tinyproxy_enable="yes"
822 {% else %}
823 pen_wrapper_enable="yes"
824 {% endif %}
825
826 {% if service_accesspoint %}
827 pf_flags="$pf_flags -D captive_portal_interfaces=$captive_portal_interfaces"
828 {% endif %}
829
830 {% if board == "ALIX2" %}
831 #
832 # ''Fat'' configuration, board has 256MB RAM
833 #
834 dnsmasq_enable="NO"
835 named_enable="YES"
836 {% if autogen_dhcp_interfaces -%}
837 dhcpd_enable="YES"
838 dhcpd_flags="$dhcpd_flags {{ autogen_dhcp_interfaces|join(' ') }}"
839 {% endif -%}
840 {% endif -%}
841{% elif nodetype == "CNode" %}
842#
843# NODE iLeiden Configuration
844#
845
846# iLeiden Proxies {{ autogen_ileiden_proxies_names }}
847list_ileiden_proxies="{{ autogen_ileiden_proxies_ips }}"
848# normal Proxies {{ autogen_normal_proxies_names }}
849list_normal_proxies="{{ autogen_normal_proxies_ips }}"
850
851captive_portal_interfaces="{{ autogen_dhcp_interfaces|join(',') }}"
852
853lvrouted_flags="-u -s s00p3rs3kr3t -m 28 -z $list_ileiden_proxies"
854{% endif %}
855
856#
857# Script variables
858#
859attached_devices="{{ autogen_attached_devices|join(' ') }}"
860neighbours="{{ autogen_neighbours|join(' ') }}"
861
862
863#
864# Interface definitions
865#\n
866""")
867
868 (addrs_list, dhclient_if, extra_ouput) = make_interface_list(datadump)
869 output += extra_ouput.strip() + "\n"
870
871 # Print IP address which needs to be assigned over here
872 output += "\n"
873 for iface,addrs in sorted(addrs_list.iteritems()):
874 for addr, comment in sorted(addrs,key=lambda x: parseaddr(x[0].split('/')[0])):
875 output += "# %s || %s || %s\n" % (iface, addr, comment)
876
877 # Write DHCLIENT entry
878 if dhclient_if[iface]:
879 output += "ifconfig_%s='SYNCDHCP'\n\n" % (iface)
880
881 # Make sure the external address is always first as this is needed in the
882 # firewall setup
883 addrs = sorted(
884 [x for x in addrs if not '0.0.0.0' in x[0]],
885 key=lambda x: x[0].split('.')[0],
886 cmp=lambda x,y: cmp(1 if x == '172' else 0, 1 if y == '172' else 0)
887 )
888 addr_str = " ".join([x[0] for x in addrs])
889 output += "ipv4_addrs_%s='%s'\n\n" % (iface, addr_str)
890
891 rc_conf_local_cache[datadump['autogen_item']] = output
892 return output
893
894
895
896
897def get_all_configs():
898 """ Get dict with key 'host' with all configs present """
899 configs = dict()
900 for host in get_hostlist():
901 datadump = get_yaml(host)
902 configs[host] = datadump
903 return configs
904
905
906def get_interface_keys(config, extra=False):
907 """ Quick hack to get all interface keys, later stage convert this to a iterator """
908 elems = sorted([elem for elem in config.keys() if (elem.startswith('iface_') and not "lo0" in elem)])
909 if extra == False:
910 return filter(lambda x: not "extra" in x, elems)
911 else:
912 return elems
913
914
915def get_used_ips(configs):
916 """ Return array of all IPs used in config files"""
917 ip_list = []
918 for config in configs:
919 ip_list.append(config['masterip'])
920 for iface_key in get_interface_keys(config, True):
921 l = config[iface_key]['ip']
922 addr, mask = l.split('/')
923 # Special case do not process
924 if valid_addr(addr):
925 ip_list.append(addr)
926 else:
927 logger.error("## IP '%s' in '%s' not valid" % (addr, config['nodename']))
928 return sorted(ip_list)
929
930
931
932def get_nameservers(max_servers=None):
933 if nameservers_cache:
934 return nameservers_cache[0:max_servers]
935
936 for host in get_hybridlist():
937 hostdump = get_yaml(host)
938 if hostdump['status'] == 'up' and (hostdump['service_proxy_ileiden'] or hostdump['service_proxy_normal']):
939 nameservers_cache.append((hostdump['masterip'], hostdump['autogen_realname']))
940 for host in get_proxylist():
941 hostdump = get_yaml(host)
942 if hostdump['status'] == 'up':
943 nameservers_cache.append((hostdump['masterip'], hostdump['autogen_realname']))
944
945 return nameservers_cache[0:max_servers]
946
947
948def get_neighbours(datadump):
949 (addrs_list, dhclient_if, extra_ouput) = make_interface_list(datadump)
950
951 (poel, errors) = make_relations()
952 table = []
953 for iface,addrs in sorted(addrs_list.iteritems()):
954 if iface in ['lo0']:
955 continue
956
957 for addr, comment in sorted(addrs,key=lambda x: parseaddr(x[0].split('/')[0])):
958 if not addr.startswith('172.'):
959 # Avoid listing internet connections as pool
960 continue
961 for neighbour in poel[network(addr)]:
962 if neighbour[0] != datadump['autogen_item']:
963 table.append((iface, neighbour[1]['ip'].split('/')[0], neighbour[0] + " (" + neighbour[1]['autogen_ifname'] + ")", neighbour[1]['comment']))
964 return table
965
966
967def get_attached_devices(datadump, url=False):
968 table = []
969 for iface_key in get_interface_keys(datadump, True):
970 # Quick to avoid listing ath(4) interface as attached device
971 if 'ath0' in iface_key:
972 continue
973 ifacedump = datadump[iface_key]
974 if ifacedump.has_key('ns_ip'):
975 x_ip = ifacedump['ns_ip'].split('/')[0]
976 else:
977 x_ip = ifacedump['ip'].split('/')[0]
978
979 if 'mode' in ifacedump:
980 x_mode = ifacedump['mode']
981 else:
982 x_mode = 'unknown'
983
984 if 'bridge_type' in ifacedump:
985 device_type = ifacedump['bridge_type']
986 else:
987 device_type = 'Unknown'
988
989 table.append((ifacedump['autogen_ifname'], x_mode, 'http://%s' % x_ip if url else x_ip, device_type))
990 return table
991
992
993def generate_resolv_conf(datadump):
994 """ Generate configuration file '/etc/resolv.conf' """
995 # XXX: This should properly going to be an datastructure soon
996 datadump['autogen_header'] = generate_header(datadump, "#")
997 datadump['autogen_edge_nameservers'] = ''
998
999
1000 for masterip,realname in get_nameservers():
1001 datadump['autogen_edge_nameservers'] += "nameserver %-15s # %s\n" % (masterip, realname)
1002
1003 return Template("""\
1004{{ autogen_header }}
1005search wleiden.net
1006
1007# Try local (cache) first
1008nameserver 127.0.0.1
1009
1010{% if service_proxy_normal or service_proxy_ileiden or nodetype == 'Proxy' -%}
1011nameserver 8.8.8.8 # Google Public NameServer
1012nameserver 4.2.2.1 # Level3 Public NameServer
1013{% else -%}
1014# START DYNAMIC LIST - updated by /tools/nameserver-shuffle
1015{{ autogen_edge_nameservers }}
1016{% endif -%}
1017""").render(datadump)
1018
1019
1020
1021def generate_ntp_conf(datadump):
1022 """ Generate configuration file '/etc/ntp.conf' """
1023 # XXX: This should properly going to be an datastructure soon
1024
1025 datadump['autogen_header'] = generate_header(datadump, "#")
1026 datadump['autogen_ntp_servers'] = ''
1027 for host in get_proxylist():
1028 hostdump = get_yaml(host)
1029 datadump['autogen_ntp_servers'] += "server %(masterip)-15s iburst maxpoll 9 # %(autogen_realname)s\n" % hostdump
1030 for host in get_hybridlist():
1031 hostdump = get_yaml(host)
1032 if hostdump['service_proxy_ileiden'] or hostdump['service_proxy_normal']:
1033 datadump['autogen_ntp_servers'] += "server %(masterip)-15s iburst maxpoll 9 # %(autogen_realname)s\n" % hostdump
1034
1035 return Template("""\
1036{{ autogen_header }}
1037
1038{% if service_proxy_normal or service_proxy_ileiden or nodetype == 'Proxy' -%}
1039# Machine hooked to internet.
1040server 0.nl.pool.ntp.org iburst maxpoll 9
1041server 1.nl.pool.ntp.org iburst maxpoll 9
1042server 2.nl.pool.ntp.org iburst maxpoll 9
1043server 3.nl.pool.ntp.org iburst maxpoll 9
1044{% else -%}
1045# Local Wireless Leiden NTP Servers.
1046server 0.pool.ntp.wleiden.net iburst maxpoll 9
1047server 1.pool.ntp.wleiden.net iburst maxpoll 9
1048server 2.pool.ntp.wleiden.net iburst maxpoll 9
1049server 3.pool.ntp.wleiden.net iburst maxpoll 9
1050
1051# All the configured NTP servers
1052{{ autogen_ntp_servers }}
1053{% endif %}
1054
1055# If a server loses sync with all upstream servers, NTP clients
1056# no longer follow that server. The local clock can be configured
1057# to provide a time source when this happens, but it should usually
1058# be configured on just one server on a network. For more details see
1059# http://support.ntp.org/bin/view/Support/UndisciplinedLocalClock
1060# The use of Orphan Mode may be preferable.
1061#
1062server 127.127.1.0
1063fudge 127.127.1.0 stratum 10
1064""").render(datadump)
1065
1066
1067def generate_pf_hybrid_conf_local(datadump):
1068 """ Generate configuration file '/etc/pf.hybrid.conf.local' """
1069 datadump['autogen_header'] = generate_header(datadump, "#")
1070 return Template("""\
1071{{ autogen_header }}
1072
1073# Redirect some internal facing services outside (7)
1074# INFO: {{ rdr_rules|count }} rdr_rules (outside to internal redirect rules) defined.
1075{% for protocol, src_port,dest_ip,dest_port in rdr_rules -%}
1076rdr on $ext_if inet proto {{ protocol }} from any to $ext_if port {{ src_port }} tag SRV -> {{ dest_ip }} port {{ dest_port }}
1077{% endfor -%}
1078""").render(datadump)
1079
1080def generate_motd(datadump):
1081 """ Generate configuration file '/etc/motd' """
1082 output = Template("""\
1083FreeBSD run ``service motd onestart'' to make me look normal
1084
1085 WWW: {{ autogen_fqdn }} - http://www.wirelessleiden.nl
1086 Loc: {{ location }}
1087
1088Services:
1089{% if board == "ALIX2" -%}
1090{{" -"}} Core Node ({{ board }})
1091{% else -%}
1092{{" -"}} Hulp Node ({{ board }})
1093{% endif -%}
1094{% if service_proxy_normal -%}
1095{{" -"}} Normal Proxy
1096{% endif -%}
1097{% if service_proxy_ileiden -%}
1098{{" -"}} iLeiden Proxy
1099{% endif -%}
1100{% if service_incoming_rdr -%}
1101{{" -"}} Incoming port redirects
1102{% endif %}
1103Interlinks:\n
1104""").render(datadump)
1105
1106
1107 def make_table(table):
1108 if not table:
1109 return " - none\n"
1110 else:
1111 lines = ""
1112 col_width = [max(len(x) for x in col) for col in zip(*table)]
1113 for row in table:
1114 lines += " - " + " || ".join("{:{}}".format(x, col_width[i]) for i, x in enumerate(row)) + "\n"
1115 return lines
1116
1117 (addrs_list, dhclient_if, extra_ouput) = make_interface_list(datadump)
1118 table = []
1119 for iface,addrs in sorted(addrs_list.iteritems()):
1120 if iface in ['lo0']:
1121 continue
1122 for addr, comment in sorted(addrs,key=lambda x: parseaddr(x[0].split('/')[0])):
1123 table.append((iface, addr, comment))
1124
1125 output += make_table(table)
1126 output += '\n'
1127 output += """\
1128Attached devices:
1129"""
1130 output += make_table(get_attached_devices(datadump, url=True))
1131 output += '\n'
1132 output += """\
1133Available neighbours:
1134"""
1135 output += make_table(get_neighbours(datadump))
1136
1137 return output
1138
1139
1140def format_yaml_value(value):
1141 """ Get yaml value in right syntax for outputting """
1142 if isinstance(value,str):
1143 output = '"%s"' % value
1144 else:
1145 output = value
1146 return output
1147
1148
1149
1150def format_wleiden_yaml(datadump):
1151 """ Special formatting to ensure it is editable"""
1152 output = "# Genesis config yaml style\n"
1153 output += "# vim:ts=2:et:sw=2:ai\n"
1154 output += "#\n"
1155 iface_keys = [elem for elem in datadump.keys() if elem.startswith('iface_')]
1156 for key in sorted(set(datadump.keys()) - set(iface_keys)):
1157 if key == 'rdr_rules':
1158 output += '%-10s:\n' % 'rdr_rules'
1159 for rdr_rule in datadump[key]:
1160 output += '- %s\n' % rdr_rule
1161 else:
1162 output += "%-10s: %s\n" % (key, format_yaml_value(datadump[key]))
1163
1164 output += "\n\n"
1165
1166 # Format (key, required)
1167 key_order = (
1168 ('comment', True),
1169 ('ip', True),
1170 ('desc', True),
1171 ('sdesc', True),
1172 ('mode', True),
1173 ('type', True),
1174 ('extra_type', False),
1175 ('channel', False),
1176 ('ssid', False),
1177 ('wlan_mac', False),
1178 ('dhcp', True),
1179 ('compass', False),
1180 ('distance', False),
1181 ('ns_ip', False),
1182 ('repeater_ip', False),
1183 ('bullet2_ip', False),
1184 ('ns_mac', False),
1185 ('bullet2_mac', False),
1186 ('ns_type', False),
1187 ('bridge_type', False),
1188 ('members', True),
1189 ('status', True),
1190 )
1191
1192 for iface_key in sorted(iface_keys):
1193 try:
1194 remainder = set(datadump[iface_key].keys()) - set([x[0] for x in key_order])
1195 if remainder:
1196 raise KeyError("invalid keys: %s" % remainder)
1197
1198 output += "%s:\n" % iface_key
1199 for key,required in key_order:
1200 if datadump[iface_key].has_key(key):
1201 output += " %-11s: %s\n" % (key, format_yaml_value(datadump[iface_key][key]))
1202 output += "\n\n"
1203 except Exception as e:
1204 print "# Error while processing interface %s" % iface_key
1205 raise
1206
1207 return output
1208
1209
1210
1211def generate_wleiden_yaml(datadump, header=True):
1212 """ Generate (petty) version of wleiden.yaml"""
1213 output = generate_header(datadump, "#") if header else ''
1214
1215 for key in datadump.keys():
1216 if key.startswith('autogen_'):
1217 del datadump[key]
1218 # Interface autogen cleanups
1219 elif type(datadump[key]) == dict:
1220 for key2 in datadump[key].keys():
1221 if key2.startswith('autogen_'):
1222 del datadump[key][key2]
1223
1224 output += format_wleiden_yaml(datadump)
1225 return output
1226
1227def generate_nanostation_config(datadump, iface, ns_type):
1228 #TODO(rvdz): Make sure the proper nanostation IP and subnet is set
1229 datadump['iface_%s' % iface]['ns_ip'] = datadump['iface_%s' % iface]['ns_ip'].split('/')[0]
1230
1231 datadump.update(datadump['iface_%s' % iface])
1232
1233 return open(os.path.join(os.path.dirname(__file__), 'ns5m.cfg.tmpl'),'r').read() % datadump
1234
1235def generate_yaml(datadump):
1236 return generate_config(datadump['nodename'], "wleiden.yaml", datadump)
1237
1238
1239
1240def generate_config(node, config, datadump=None):
1241 """ Print configuration file 'config' of 'node' """
1242 output = ""
1243 try:
1244 # Load config file
1245 if datadump == None:
1246 datadump = get_yaml(node)
1247
1248 if config == 'wleiden.yaml':
1249 output += generate_wleiden_yaml(datadump)
1250 elif config == 'authorized_keys':
1251 f = open(os.path.join(NODE_DIR,"global_keys"), 'r')
1252 output += f.read()
1253 node_keys = os.path.join(NODE_DIR,node,'authorized_keys')
1254 # Fetch local keys if existing
1255 if os.path.exists(node_keys):
1256 output += open(node_keys, 'r').read()
1257 f.close()
1258 elif config == 'dnsmasq.conf':
1259 output += generate_dnsmasq_conf(datadump)
1260 elif config == 'dhcpd.conf':
1261 output += generate_dhcpd_conf(datadump)
1262 elif config == 'rc.conf.local':
1263 output += generate_rc_conf_local(datadump)
1264 elif config == 'resolv.conf':
1265 output += generate_resolv_conf(datadump)
1266 elif config == 'ntp.conf':
1267 output += generate_ntp_conf(datadump)
1268 elif config == 'motd':
1269 output += generate_motd(datadump)
1270 elif config == 'pf.hybrid.conf.local':
1271 output += generate_pf_hybrid_conf_local(datadump)
1272 elif config.startswith('vr'):
1273 interface, ns_type = config.strip('.yaml').split('-')
1274 output += generate_nanostation_config(datadump, interface, ns_type)
1275 else:
1276 assert False, "Config not found!"
1277 except IOError, e:
1278 output += "[ERROR] Config file not found"
1279 return output
1280
1281
1282
1283def process_cgi_request(environ=os.environ):
1284 """ When calling from CGI """
1285 response_headers = []
1286 content_type = 'text/plain'
1287
1288 # Update repository if requested
1289 form = urlparse.parse_qs(environ['QUERY_STRING']) if environ.has_key('QUERY_STRING') else None
1290 if form and form.has_key("action") and "update" in form["action"]:
1291 output = "[INFO] Updating subverion, please wait...\n"
1292 output += subprocess.Popen([SVN, 'cleanup', "%s/.." % NODE_DIR], stderr=subprocess.STDOUT, stdout=subprocess.PIPE).communicate()[0]
1293 output += subprocess.Popen([SVN, 'up', "%s/.." % NODE_DIR], stderr=subprocess.STDOUT, stdout=subprocess.PIPE).communicate()[0]
1294 output += "[INFO] All done, redirecting in 5 seconds"
1295 response_headers += [
1296 ('Refresh', '5; url=.'),
1297 ]
1298 reload_cache()
1299 else:
1300 base_uri = environ['PATH_INFO']
1301 uri = base_uri.strip('/').split('/')
1302
1303 output = "Template Holder"
1304 if base_uri.endswith('/create/network.kml'):
1305 content_type='application/vnd.google-earth.kml+xml'
1306 output = make_network_kml.make_graph()
1307 elif base_uri.endswith('/api/get/nodeplanner.json'):
1308 content_type='application/json'
1309 output = make_network_kml.make_nodeplanner_json()
1310 elif not uri[0]:
1311 if is_text_request(environ):
1312 output = '\n'.join(get_hostlist())
1313 else:
1314 content_type = 'text/html'
1315 output = generate_title(get_hostlist())
1316 elif len(uri) == 1:
1317 if is_text_request(environ):
1318 output = generate_node(uri[0])
1319 else:
1320 content_type = 'text/html'
1321 output = generate_node_overview(uri[0])
1322 elif len(uri) == 2:
1323 output = generate_config(uri[0], uri[1])
1324 else:
1325 assert False, "Invalid option"
1326
1327 # Return response
1328 response_headers += [
1329 ('Content-type', content_type),
1330 ('Content-Length', str(len(output))),
1331 ]
1332 return(response_headers, str(output))
1333
1334
1335def get_realname(datadump):
1336 # Proxy naming convention is special, as the proxy name is also included in
1337 # the nodename, when it comes to the numbered proxies.
1338 if datadump['nodetype'] == 'Proxy':
1339 realname = datadump['nodetype'] + datadump['nodename'].replace('proxy','')
1340 else:
1341 # By default the full name is listed and also a shortname CNAME for easy use.
1342 realname = datadump['nodetype'] + datadump['nodename']
1343 return(realname)
1344
1345
1346
1347def make_dns(output_dir = 'dns', external = False):
1348 items = dict()
1349
1350 # hostname is key, IP is value
1351 wleiden_zone = defaultdict(list)
1352 wleiden_cname = dict()
1353
1354 pool = dict()
1355 for node in get_hostlist():
1356 datadump = get_yaml(node)
1357
1358 # Proxy naming convention is special
1359 fqdn = datadump['autogen_realname']
1360 if datadump['nodetype'] in ['CNode', 'Hybrid']:
1361 wleiden_cname[datadump['nodename']] = fqdn
1362
1363 if datadump.has_key('rdr_host'):
1364 remote_target = datadump['rdr_host']
1365 elif datadump.has_key('remote_access') and datadump['remote_access']:
1366 remote_target = datadump['remote_access'].split(':')[0]
1367 else:
1368 remote_target = None
1369
1370 if remote_target:
1371 try:
1372 parseaddr(remote_target)
1373 wleiden_zone[datadump['nodename'] + '.gw'].append((remote_target, False))
1374 except (IndexError, ValueError):
1375 wleiden_cname[datadump['nodename'] + '.gw'] = remote_target + '.'
1376
1377
1378 wleiden_zone[fqdn].append((datadump['masterip'], True))
1379
1380 # Hacking to get proper DHCP IPs and hostnames
1381 for iface_key in get_interface_keys(datadump):
1382 iface_name = iface_key.replace('_','-')
1383 (ip, cidr) = datadump[iface_key]['ip'].split('/')
1384 try:
1385 (dhcp_start, dhcp_stop) = datadump[iface_key]['dhcp'].split('-')
1386 datadump[iface_key]['autogen_netmask'] = cidr2netmask(cidr)
1387 dhcp_part = ".".join(ip.split('.')[0:3])
1388 if ip != datadump['masterip']:
1389 wleiden_zone["dhcp-gateway-%s.%s" % (iface_name, fqdn)].append((ip, False))
1390 for i in range(int(dhcp_start), int(dhcp_stop) + 1):
1391 wleiden_zone["dhcp-%s-%s.%s" % (i, iface_name, fqdn)].append(("%s.%s" % (dhcp_part, i), True))
1392 except (AttributeError, ValueError, KeyError):
1393 # First push it into a pool, to indentify the counter-part later on
1394 addr = parseaddr(ip)
1395 cidr = int(cidr)
1396 addr = addr & ~((1 << (32 - cidr)) - 1)
1397 if pool.has_key(addr):
1398 pool[addr] += [(iface_name, fqdn, ip)]
1399 else:
1400 pool[addr] = [(iface_name, fqdn, ip)]
1401 continue
1402
1403
1404
1405 # WL uses an /29 to configure an interface. IP's are ordered like this:
1406 # MasterA (.1) -- DeviceA (.2) <<>> DeviceB (.3) --- SlaveB (.4)
1407
1408 sn = lambda x: re.sub(r'(?i)^cnode','',x)
1409
1410 # Automatic naming convention of interlinks namely 2 + remote.lower()
1411 for (key,value) in pool.iteritems():
1412 # Make sure they are sorted from low-ip to high-ip
1413 value = sorted(value, key=lambda x: parseaddr(x[2]))
1414
1415 if len(value) == 1:
1416 (iface_name, fqdn, ip) = value[0]
1417 wleiden_zone["2unused-%s.%s" % (iface_name, fqdn)].append((ip, True))
1418
1419 # Device DNS names
1420 if 'cnode' in fqdn.lower():
1421 wleiden_zone["d-at-%s.%s" % (iface_name, fqdn)].append((showaddr(parseaddr(ip) + 1), False))
1422 wleiden_cname["d-at-%s.%s" % (iface_name,sn(fqdn))] = "d-at-%s.%s" % ((iface_name, fqdn))
1423
1424 elif len(value) == 2:
1425 (a_iface_name, a_fqdn, a_ip) = value[0]
1426 (b_iface_name, b_fqdn, b_ip) = value[1]
1427 wleiden_zone["2%s.%s" % (b_fqdn,a_fqdn)].append((a_ip, True))
1428 wleiden_zone["2%s.%s" % (a_fqdn,b_fqdn)].append((b_ip, True))
1429
1430 # Device DNS names
1431 if 'cnode' in a_fqdn.lower() and 'cnode' in b_fqdn.lower():
1432 wleiden_zone["d-at-%s.%s" % (a_iface_name, a_fqdn)].append((showaddr(parseaddr(a_ip) + 1), False))
1433 wleiden_zone["d-at-%s.%s" % (b_iface_name, b_fqdn)].append((showaddr(parseaddr(b_ip) - 1), False))
1434 wleiden_cname["d-at-%s.%s" % (a_iface_name,sn(a_fqdn))] = "d-at-%s.%s" % (a_iface_name, a_fqdn)
1435 wleiden_cname["d-at-%s.%s" % (b_iface_name,sn(b_fqdn))] = "d-at-%s.%s" % (b_iface_name, b_fqdn)
1436 wleiden_cname["d2%s.%s" % (sn(b_fqdn),sn(a_fqdn))] = "d-at-%s.%s" % (a_iface_name, a_fqdn)
1437 wleiden_cname["d2%s.%s" % (sn(a_fqdn),sn(b_fqdn))] = "d-at-%s.%s" % (b_iface_name, b_fqdn)
1438
1439 else:
1440 pool_members = [k[1] for k in value]
1441 for item in value:
1442 (iface_name, fqdn, ip) = item
1443 wleiden_zone["2ring.%s" % (fqdn)].append((ip, True))
1444
1445 # Include static DNS entries
1446 # XXX: Should they override the autogenerated results?
1447 # XXX: Convert input to yaml more useable.
1448 # Format:
1449 ##; this is a comment
1450 ## roomburgh=CNodeRoomburgh1
1451 ## apkerk1.CNodeVosko=172.17.176.8 ;this as well
1452 dns_list = yaml.load(open(os.path.join(NODE_DIR,'../dns/staticDNS.yaml'),'r'))
1453
1454 # Hack to allow special entries, for development
1455 wleiden_raw = {}
1456
1457 for line in dns_list:
1458 reverse = False
1459 k, items = line.items()[0]
1460 if type(items) == dict:
1461 if items.has_key('reverse'):
1462 reverse = items['reverse']
1463 items = items['a']
1464 else:
1465 items = items['cname']
1466 items = [items] if type(items) != list else items
1467 for item in items:
1468 if item.startswith('IN '):
1469 wleiden_raw[k] = item
1470 elif valid_addr(item):
1471 wleiden_zone[k].append((item, reverse))
1472 else:
1473 wleiden_cname[k] = item
1474
1475 # Hack to get dynamic pool listing
1476 def chunks(l, n):
1477 return [l[i:i+n] for i in range(0, len(l), n)]
1478
1479 ntp_servers = [x[0] for x in get_nameservers()]
1480 for id, chunk in enumerate(chunks(ntp_servers,(len(ntp_servers)/4))):
1481 for ntp_server in chunk:
1482 wleiden_zone['%i.pool.ntp' % id].append((ntp_server, False))
1483
1484 details = dict()
1485 # 24 updates a day allowed
1486 details['serial'] = time.strftime('%Y%m%d%H')
1487
1488 if external:
1489 dns_masters = ['siteview.wirelessleiden.nl', 'ns1.vanderzwet.net']
1490 else:
1491 dns_masters = ['sunny.wleiden.net'] + ["%s.wleiden.net" % x[1] for x in get_nameservers(max_servers=3)]
1492
1493 details['master'] = dns_masters[0]
1494 details['ns_servers'] = '\n'.join(['\tNS\t%s.' % x for x in dns_masters])
1495
1496 dns_header = '''
1497$TTL 3h
1498%(zone)s. SOA %(master)s. beheer.lijst.wirelessleiden.nl. ( %(serial)s 15m 15m 1w 60s )
1499 ; Serial, Refresh, Retry, Expire, Neg. cache TTL
1500
1501%(ns_servers)s
1502 \n'''
1503
1504
1505 if not os.path.isdir(output_dir):
1506 os.makedirs(output_dir)
1507 details['zone'] = 'wleiden.net'
1508 f = open(os.path.join(output_dir,"db." + details['zone']), "w")
1509 f.write(dns_header % details)
1510
1511 for host,items in wleiden_zone.iteritems():
1512 for ip,reverse in items:
1513 if ip not in ['0.0.0.0']:
1514 f.write("%s.wleiden.net. IN A %s \n" % (host.lower(), ip))
1515 for source,dest in wleiden_cname.iteritems():
1516 dest = dest if dest.endswith('.') else dest + ".wleiden.net."
1517 f.write("%s.wleiden.net. IN CNAME %s\n" % (source.lower(), dest.lower()))
1518 for source, dest in wleiden_raw.iteritems():
1519 f.write("%s.wleiden.net. %s\n" % (source, dest))
1520 f.close()
1521
1522 # Create whole bunch of specific sub arpa zones. To keep it compliant
1523 for s in range(16,32):
1524 details['zone'] = '%i.172.in-addr.arpa' % s
1525 f = open(os.path.join(output_dir,"db." + details['zone']), "w")
1526 f.write(dns_header % details)
1527
1528 #XXX: Not effient, fix to proper data structure and do checks at other
1529 # stages
1530 for host,items in wleiden_zone.iteritems():
1531 for ip,reverse in items:
1532 if not reverse:
1533 continue
1534 if valid_addr(ip):
1535 if valid_addr(ip):
1536 if int(ip.split('.')[1]) == s:
1537 rev_ip = '.'.join(reversed(ip.split('.')))
1538 f.write("%s.in-addr.arpa. IN PTR %s.wleiden.net.\n" % (rev_ip.lower(), host.lower()))
1539 f.close()
1540
1541
1542def usage():
1543 print """Usage: %(prog)s <argument>
1544Argument:
1545\tcleanup = Cleanup all YAML files to specified format
1546\tstandalone [port] = Run configurator webserver [8000]
1547\tdns [outputdir] = Generate BIND compliant zone files in dns [./dns]
1548\tnagios-export [--heavy-load] = Generate basic nagios configuration file.
1549\tfull-export = Generate yaml export script for heatmap.
1550\tstatic [outputdir] = Generate all config files and store on disk
1551\t with format ./<outputdir>/%%NODE%%/%%FILE%% [./static]
1552\ttest <node> [<file>] = Receive output for certain node [all files].
1553\ttest-cgi <node> <file> = Receive output of CGI script [all files].
1554\tlist <status> <items> = List systems which have certain status
1555
1556Arguments:
1557\t<node> = NodeName (example: HybridRick)
1558\t<file> = %(files)s
1559\t<status> = all|up|down|planned
1560\t<items> = systems|nodes|proxies
1561
1562NOTE FOR DEVELOPERS; you can test your changes like this:
1563 BEFORE any changes in this code:
1564 $ ./gformat.py static /tmp/pre
1565 AFTER the changes:
1566 $ ./gformat.py static /tmp/post
1567 VIEW differences and VERIFY all are OK:
1568 $ diff -urI 'Generated' -r /tmp/pre /tmp/post
1569""" % { 'prog' : sys.argv[0], 'files' : '|'.join(files) }
1570 exit(0)
1571
1572
1573def is_text_request(environ=os.environ):
1574 """ Find out whether we are calling from the CLI or any text based CLI utility """
1575 try:
1576 return environ['HTTP_USER_AGENT'].split()[0] in ['curl', 'fetch', 'wget']
1577 except KeyError:
1578 return True
1579
1580def switchFormat(setting):
1581 if setting:
1582 return "YES"
1583 else:
1584 return "NO"
1585
1586def rlinput(prompt, prefill=''):
1587 import readline
1588 readline.set_startup_hook(lambda: readline.insert_text(prefill))
1589 try:
1590 return raw_input(prompt)
1591 finally:
1592 readline.set_startup_hook()
1593
1594def fix_conflict(left, right, default='i'):
1595 while True:
1596 print "## %-30s | %-30s" % (left, right)
1597 c = raw_input("## Solve Conflict (h for help) <l|r|e|i|> [%s]: " % default)
1598 if not c:
1599 c = default
1600
1601 if c in ['l','1']:
1602 return left
1603 elif c in ['r','2']:
1604 return right
1605 elif c in ['e', '3']:
1606 return rlinput("Edit: ", "%30s | %30s" % (left, right))
1607 elif c in ['i', '4']:
1608 return None
1609 else:
1610 print "#ERROR: '%s' is invalid input (left, right, edit or ignore)!" % c
1611
1612
1613
1614def print_cgi_response(response_headers, output):
1615 """Could we not use some kind of wsgi wrapper to make this output?"""
1616 for header in response_headers:
1617 print "%s: %s" % header
1618 print
1619 print output
1620
1621
1622def fill_cache():
1623 ''' Poor man re-loading of few cache items (the slow ones) '''
1624 for host in get_hostlist():
1625 get_yaml(host)
1626
1627
1628def reload_cache():
1629 clear_cache()
1630 fill_cache()
1631
1632
1633def main():
1634 """Hard working sub"""
1635 # Allow easy hacking using the CLI
1636 if not os.environ.has_key('PATH_INFO'):
1637 if len(sys.argv) < 2:
1638 usage()
1639
1640 if sys.argv[1] == "standalone":
1641 import SocketServer
1642 import CGIHTTPServer
1643 # Hop to the right working directory.
1644 os.chdir(os.path.dirname(__file__))
1645 try:
1646 PORT = int(sys.argv[2])
1647 except (IndexError,ValueError):
1648 PORT = 8000
1649
1650 class MyCGIHTTPRequestHandler(CGIHTTPServer.CGIHTTPRequestHandler):
1651 """ Serve this CGI from the root of the webserver """
1652 def is_cgi(self):
1653 if "favicon" in self.path:
1654 return False
1655
1656 self.cgi_info = (os.path.basename(__file__), self.path)
1657 self.path = ''
1658 return True
1659 handler = MyCGIHTTPRequestHandler
1660 SocketServer.TCPServer.allow_reuse_address = True
1661 httpd = SocketServer.TCPServer(("", PORT), handler)
1662 httpd.server_name = 'localhost'
1663 httpd.server_port = PORT
1664
1665 logger.info("serving at port %s", PORT)
1666 try:
1667 httpd.serve_forever()
1668 except KeyboardInterrupt:
1669 httpd.shutdown()
1670 logger.info("All done goodbye")
1671 elif sys.argv[1] == "test":
1672 # Basic argument validation
1673 try:
1674 node = sys.argv[2]
1675 datadump = get_yaml(node)
1676 except IndexError:
1677 print "Invalid argument"
1678 exit(1)
1679 except IOError as e:
1680 print e
1681 exit(1)
1682
1683
1684 # Get files to generate
1685 gen_files = sys.argv[3:] if len(sys.argv) > 3 else files
1686
1687 # Actual config generation
1688 for config in gen_files:
1689 logger.info("## Generating %s %s", node, config)
1690 print generate_config(node, config, datadump)
1691 elif sys.argv[1] == "test-cgi":
1692 os.environ['PATH_INFO'] = "/".join(sys.argv[2:])
1693 os.environ['SCRIPT_NAME'] = __file__
1694 response_headers, output = process_cgi_request()
1695 print_cgi_response(response_headers, output)
1696 elif sys.argv[1] == "static":
1697 items = dict()
1698 items['output_dir'] = sys.argv[2] if len(sys.argv) > 2 else "./static"
1699 for node in get_hostlist():
1700 items['node'] = node
1701 items['wdir'] = "%(output_dir)s/%(node)s" % items
1702 if not os.path.isdir(items['wdir']):
1703 os.makedirs(items['wdir'])
1704 datadump = get_yaml(node)
1705 for config in files:
1706 items['config'] = config
1707 logger.info("## Generating %(node)s %(config)s" % items)
1708 f = open("%(wdir)s/%(config)s" % items, "w")
1709 f.write(generate_config(node, config, datadump))
1710 f.close()
1711 elif sys.argv[1] == "wind-export":
1712 items = dict()
1713 for node in get_hostlist():
1714 datadump = get_yaml(node)
1715 sql = """INSERT IGNORE INTO nodes (name, name_ns, longitude, latitude)
1716 VALUES ('%(nodename)s', '%(nodename)s', %(latitude)s, %(longitude)s);""" % datadump;
1717 sql = """INSERT IGNORE INTO users_nodes (user_id, node_id, owner)
1718 VALUES (
1719 (SELECT id FROM users WHERE username = 'rvdzwet'),
1720 (SELECT id FROM nodes WHERE name = '%(nodename)s'),
1721 'Y');""" % datadump
1722 #for config in files:
1723 # items['config'] = config
1724 # print "## Generating %(node)s %(config)s" % items
1725 # f = open("%(wdir)s/%(config)s" % items, "w")
1726 # f.write(generate_config(node, config, datadump))
1727 # f.close()
1728 for node in get_hostlist():
1729 datadump = get_yaml(node)
1730 for iface_key in sorted([elem for elem in datadump.keys() if elem.startswith('iface_')]):
1731 ifacedump = datadump[iface_key]
1732 if ifacedump.has_key('mode') and ifacedump['mode'] == 'ap-wds':
1733 ifacedump['nodename'] = datadump['nodename']
1734 if not ifacedump.has_key('channel') or not ifacedump['channel']:
1735 ifacedump['channel'] = 0
1736 sql = """INSERT INTO links (node_id, type, ssid, protocol, channel, status)
1737 VALUES ((SELECT id FROM nodes WHERE name = '%(nodename)s'), 'ap',
1738 '%(ssid)s', 'IEEE 802.11b', %(channel)s, 'active');""" % ifacedump
1739 elif sys.argv[1] == "nagios-export":
1740 try:
1741 heavy_load = (sys.argv[2] == "--heavy-load")
1742 except IndexError:
1743 heavy_load = False
1744
1745 hostgroup_details = {
1746 'wleiden' : 'Stichting Wireless Leiden - FreeBSD Nodes',
1747 'wzoeterwoude' : 'Stichting Wireless Leiden - Afdeling Zoeterwoude - Free-WiFi Project',
1748 'walphen' : 'Stichting Wireless Alphen',
1749 'westeinder' : 'Westeinder Plassen',
1750 }
1751
1752 # Convert IP to Host
1753 ip2host = {'root' : 'root'}
1754 for host in get_hostlist():
1755 datadump = get_yaml(host)
1756 ip2host[datadump['masterip']] = datadump['autogen_fqdn']
1757 for iface in get_interface_keys(datadump):
1758 ip2host[datadump[iface]['autogen_gateway']] = datadump['autogen_fqdn']
1759
1760 # Find dependency tree based on output of lvrouted.mytree of nearest node
1761 parents = defaultdict(list)
1762 stack = ['root']
1763 prev_depth = 0
1764 for line in open('lvrouted.mytree').readlines():
1765 depth = line.count('\t')
1766 ip = line.strip().split()[0]
1767
1768 if prev_depth < depth:
1769 try:
1770 parents[ip2host[ip]].append(ip2host[stack[-1]])
1771 except KeyError as e:
1772 print >> stderr, "# Unable to find %s in configuration files" % e.args[0]
1773 stack.append(ip)
1774 elif prev_depth > depth:
1775 stack = stack[:(depth - prev_depth)]
1776 elif prev_depth == depth:
1777 try:
1778 parents[ip2host[ip]].append(ip2host[stack[-1]])
1779 except KeyError as e:
1780 print >> stderr, "# Unable to find %s in configuration files" % e.args[0]
1781
1782
1783 prev_depth = depth
1784 # Observe that some nodes has themself as parent or multiple parents
1785 # for now take only the first parent, other behaviour is yet to be explained
1786
1787
1788
1789 params = {
1790 'check_interval' : 5 if heavy_load else 120,
1791 'retry_interval' : 1 if heavy_load else 10,
1792 'max_check_attempts' : 10 if heavy_load else 6,
1793 'notification_interval': 120 if heavy_load else 240,
1794 }
1795
1796 print '''\
1797define host {
1798 name wleiden-node ; Default Node Template
1799 use generic-host ; Use the standard template as initial starting point
1800 check_period 24x7 ; By default, FreeBSD hosts are checked round the clock
1801 check_interval %(check_interval)s ; Actively check the host every 5 minutes
1802 retry_interval %(retry_interval)s ; Schedule host check retries at 1 minute intervals
1803 notification_interval %(notification_interval)s
1804 max_check_attempts %(max_check_attempts)s ; Check each FreeBSD host 10 times (max)
1805 check_command check-host-alive ; Default command to check FreeBSD hosts
1806 register 0 ; DONT REGISTER THIS DEFINITION - ITS NOT A REAL HOST, JUST A TEMPLATE!
1807}
1808
1809define service {
1810 name wleiden-service ; Default Service Template
1811 use generic-service ; Use the standard template as initial starting point
1812 check_period 24x7 ; By default, FreeBSD hosts are checked round the clock
1813 check_interval %(check_interval)s ; Actively check the host every 5 minutes
1814 retry_interval %(retry_interval)s ; Schedule host check retries at 1 minute intervals
1815 notification_interval %(notification_interval)s
1816 max_check_attempts %(max_check_attempts)s ; Check each FreeBSD host 10 times (max)
1817 register 0 ; DONT REGISTER THIS DEFINITION - ITS NOT A REAL HOST, JUST A TEMPLATE!
1818}
1819
1820# Please make sure to install:
1821# make -C /usr/ports/net-mgmt/nagios-check_netsnmp install clean
1822#
1823# Recompile net-mgmt/nagios-plugins to support check_snmp
1824# make -C /usr/ports/net-mgmt/nagios-plugins
1825#
1826# Install net/bind-tools to allow v2/check_dns_wl to work:
1827# pkg install bind-tools
1828#
1829define command{
1830 command_name check_snmp_disk
1831 command_line $USER1$/check_snmp_disk -H $HOSTADDRESS$ -C public
1832}
1833
1834define command{
1835 command_name check_netsnmp_load
1836 command_line $USER1$/check_snmp_load.pl -H $HOSTADDRESS$ -C public -w 80 -c 90
1837}
1838
1839define command{
1840 command_name check_netsnmp_proc
1841 command_line $USER1$/check_snmp_proc -H $HOSTADDRESS$ -C public
1842}
1843
1844define command{
1845 command_name check_by_ssh
1846 command_line $USER1$/check_by_ssh -H $HOSTADDRESS$ -p $ARG1$ -C "$ARG2$ $ARG3$ $ARG4$ $ARG5$ $ARG6$"
1847}
1848
1849define command{
1850 command_name check_dns_wl
1851 command_line $USER1$/v2/check_dns_wl $HOSTADDRESS$ $ARG1$
1852}
1853
1854define command{
1855 command_name check_snmp_uptime
1856 command_line $USER1$/check_snmp -H $HOSTADDRESS$ -C public -o .1.3.6.1.2.1.1.3.0
1857}
1858
1859
1860# TDB: dhcp leases
1861# /usr/local/libexec/nagios/check_netsnmp -H 192.168.178.47 --oid 1 exec
1862
1863# TDB: internet status
1864# /usr/local/libexec/nagios/check_netsnmp -H 192.168.178.47 --oid 1 file
1865
1866# TDB: Advanced local passive checks
1867# /usr/local/libexec/nagios/check_by_ssh
1868''' % params
1869
1870 print '''\
1871# Service Group, not displayed by default
1872define hostgroup {
1873 hostgroup_name srv_hybrid
1874 alias All Hybrid Nodes
1875 register 0
1876}
1877
1878define service {
1879 use wleiden-service
1880 hostgroup_name srv_hybrid
1881 service_description SSH
1882 check_command check_ssh
1883}
1884
1885define service {
1886 use wleiden-service,service-pnp
1887 hostgroup_name srv_hybrid
1888 service_description HTTP
1889 check_command check_http
1890}
1891
1892define service {
1893 use wleiden-service
1894 hostgroup_name srv_hybrid
1895 service_description DNS
1896 check_command check_dns_wl!"www.wirelessleiden.nl"
1897}
1898'''
1899
1900 if heavy_load:
1901 print '''\
1902define service {
1903 use wleiden-service
1904 hostgroup_name srv_hybrid
1905 service_description UPTIME
1906 check_command check_snmp_uptime
1907}
1908
1909#define service {
1910# use wleiden-service
1911# hostgroup_name srv_hybrid
1912# service_description NTP
1913# check_command check_ntp_peer
1914#}
1915
1916define service {
1917 use wleiden-service
1918 hostgroup_name srv_hybrid
1919 service_description LOAD
1920 check_command check_netsnmp_load
1921}
1922
1923define service {
1924 use wleiden-service
1925 hostgroup_name srv_hybrid
1926 service_description PROC
1927 check_command check_netsnmp_proc
1928}
1929
1930define service {
1931 use wleiden-service
1932 hostgroup_name srv_hybrid
1933 service_description DISK
1934 check_command check_snmp_disk
1935}
1936'''
1937 for node in get_hostlist():
1938 datadump = get_yaml(node)
1939 if not datadump['status'] == 'up':
1940 continue
1941 if not hostgroup_details.has_key(datadump['monitoring_group']):
1942 hostgroup_details[datadump['monitoring_group']] = datadump['monitoring_group']
1943 print '''\
1944define host {
1945 use wleiden-node,host-pnp
1946 contact_groups admins
1947 host_name %(autogen_fqdn)s
1948 address %(masterip)s
1949 hostgroups srv_hybrid,%(monitoring_group)s\
1950''' % datadump
1951 if (len(parents[datadump['autogen_fqdn']]) > 0) and parents[datadump['autogen_fqdn']][0] != 'root':
1952 print '''\
1953 parents %(parents)s\
1954''' % { 'parents' : parents[datadump['autogen_fqdn']][0] }
1955 print '''\
1956}
1957'''
1958
1959
1960 for name,alias in hostgroup_details.iteritems():
1961 print '''\
1962define hostgroup {
1963 hostgroup_name %s
1964 alias %s
1965} ''' % (name, alias)
1966
1967
1968 elif sys.argv[1] == "full-export":
1969 hosts = {}
1970 for node in get_hostlist():
1971 datadump = get_yaml(node)
1972 hosts[datadump['nodename']] = datadump
1973 print yaml.dump(hosts)
1974
1975 elif sys.argv[1] == "dns":
1976 make_dns(sys.argv[2] if len(sys.argv) > 2 else 'dns', 'external' in sys.argv)
1977 elif sys.argv[1] == "cleanup":
1978 # First generate all datadumps
1979 datadumps = dict()
1980 ssid_to_node = dict()
1981 for host in get_hostlist():
1982 logger.info("# Processing: %s", host)
1983 # Set some boring default values
1984 datadump = { 'board' : 'UNKNOWN' }
1985 datadump.update(get_yaml(host))
1986 datadumps[datadump['autogen_realname']] = datadump
1987
1988 (poel, errors) = make_relations()
1989 print "\n".join(["# WARNING: %s" % x for x in errors])
1990
1991 for host,datadump in datadumps.iteritems():
1992 try:
1993 # Convert all yes and no to boolean values
1994 def fix_boolean(dump):
1995 for key in dump.keys():
1996 if type(dump[key]) == dict:
1997 dump[key] = fix_boolean(dump[key])
1998 elif str(dump[key]).lower() in ["yes", "true"]:
1999 dump[key] = True
2000 elif str(dump[key]).lower() in ["no", "false"]:
2001 # Compass richting no (Noord Oost) is valid input
2002 if key != "compass": dump[key] = False
2003 return dump
2004 datadump = fix_boolean(datadump)
2005
2006 if 'rdnap_x' in datadump and 'rdnap_y' in datadump:
2007 datadump['latitude'], datadump['longitude'] = map(lambda x: "%.5f" % x, rd2etrs(datadump['rdnap_x'], datadump['rdnap_y']))
2008 elif 'latitude' in datadump and 'longitude' in datadump:
2009 datadump['rdnap_x'], datadump['rdnap_y'] = etrs2rd(datadump['latitude'], datadump['longitude'])
2010
2011 if datadump['nodename'].startswith('Proxy'):
2012 datadump['nodename'] = datadump['nodename'].lower()
2013
2014 for iface_key in get_interface_keys(datadump):
2015 try:
2016 # All our normal wireless cards are normal APs now
2017 if datadump[iface_key]['type'] in ['11a', '11b', '11g', 'wireless']:
2018 datadump[iface_key]['mode'] = 'ap'
2019 # Wireless Leiden SSID have an consistent lowercase/uppercase
2020 if datadump[iface_key].has_key('ssid'):
2021 ssid = datadump[iface_key]['ssid']
2022 prefix = 'ap-WirelessLeiden-'
2023 if ssid.lower().startswith(prefix.lower()):
2024 datadump[iface_key]['ssid'] = prefix + ssid[len(prefix)].upper() + ssid[len(prefix) + 1:]
2025 if datadump[iface_key].has_key('ns_ip') and not datadump[iface_key].has_key('mode'):
2026 datadump[iface_key]['mode'] = 'autogen-FIXME'
2027 if not datadump[iface_key].has_key('comment'):
2028 datadump[iface_key]['comment'] = 'autogen-FIXME'
2029
2030 if datadump[iface_key].has_key('ns_mac'):
2031 datadump[iface_key]['ns_mac'] = datadump[iface_key]['ns_mac'].lower()
2032
2033 if datadump[iface_key]['comment'].startswith('autogen-') and datadump[iface_key].has_key('comment'):
2034 datadump[iface_key] = datadump[iface_key]['desc']
2035
2036 # We are not using 802.11b anymore. OFDM is preferred over DSSS
2037 # due to better collision avoidance.
2038 if datadump[iface_key]['type'] == '11b':
2039 datadump[iface_key]['type'] = '11g'
2040
2041 # Setting 802.11g channels to de-facto standards, to avoid
2042 # un-detected sharing with other overlapping channels
2043 #
2044 # Technically we could also use channel 13 in NL, but this is not
2045 # recommended as foreign devices might not be able to select this
2046 # channel. Secondly using 1,5,9,13 instead is going to clash with
2047 # the de-facto usage of 1,6,11.
2048 #
2049 # See: https://en.wikipedia.org/wiki/List_of_WLAN_channels
2050 channels_at_2400Mhz = (1,6,11)
2051 if datadump[iface_key]['type'] == '11g' and datadump[iface_key].has_key('channel'):
2052 datadump[iface_key]['channel'] = int(datadump[iface_key]['channel'])
2053 if datadump[iface_key]['channel'] not in channels_at_2400Mhz:
2054 datadump[iface_key]['channel'] = random.choice(channels_at_2400Mhz)
2055
2056 # Mandatory interface keys
2057 if not datadump[iface_key].has_key('status'):
2058 datadump[iface_key]['status'] = 'planned'
2059
2060 x = datadump[iface_key]['comment']
2061 datadump[iface_key]['comment'] = x[0].upper() + x[1:]
2062
2063 # Fixing bridge_type if none is found
2064 if datadump[iface_key].get('extra_type', '') == 'eth2wifibridge':
2065 if not 'bridge_type' in datadump[iface_key]:
2066 datadump[iface_key]['bridge_type'] = 'NanoStation M5'
2067
2068 # Making sure description works
2069 if datadump[iface_key].has_key('desc'):
2070 if datadump[iface_key]['comment'].lower() == datadump[iface_key]['desc'].lower():
2071 del datadump[iface_key]['desc']
2072 else:
2073 print "# ERROR: At %s - %s" % (datadump['nodename'], iface_key)
2074 response = fix_conflict(datadump[iface_key]['comment'], datadump[iface_key]['desc'])
2075 if response:
2076 datadump[iface_key]['comment'] = response
2077 del datadump[iface_key]['desc']
2078
2079 # Check DHCP configuration
2080 dhcp_type(datadump[iface_key])
2081
2082 # Set the compass value based on the angle between the poels
2083 if datadump[iface_key].has_key('ns_ip'):
2084 my_pool = poel[network(datadump[iface_key]['ip'])]
2085 remote_hosts = list(set([x[0] for x in my_pool]) - set([host]))
2086 if remote_hosts:
2087 compass_target = remote_hosts[0]
2088 datadump[iface_key]['compass'] = cd_between_hosts(host, compass_target, datadumps)
2089
2090 # Monitoring Group default
2091 if not 'monitoring_group' in datadump:
2092 datadump['monitoring_group'] = 'wleiden'
2093
2094 except Exception as e:
2095 print "# Error while processing interface %s" % iface_key
2096 raise
2097 store_yaml(datadump)
2098 except Exception as e:
2099 print "# Error while processing %s" % host
2100 raise
2101 elif sys.argv[1] == "list":
2102 use_fqdn = False
2103 if len(sys.argv) < 4:
2104 usage()
2105 if not sys.argv[2] in ["up", "down", "planned", "all"]:
2106 usage()
2107 if not sys.argv[3] in ["nodes","proxies","systems"]:
2108 usage()
2109
2110 if len(sys.argv) > 4:
2111 if sys.argv[4] == "fqdn":
2112 use_fqdn = True
2113 else:
2114 usage()
2115
2116 for system in get_hostlist():
2117 datadump = get_yaml(system)
2118 if sys.argv[3] == 'proxies' and not datadump['service_proxy_ileiden']:
2119 continue
2120
2121 output = datadump['autogen_fqdn'] if use_fqdn else system
2122 if sys.argv[2] == "all":
2123 print output
2124 elif datadump['status'] == sys.argv[2]:
2125 print output
2126 elif sys.argv[1] == "create":
2127 if sys.argv[2] == "network.kml":
2128 print make_network_kml.make_graph()
2129 elif sys.argv[2] == "host-ips.txt":
2130 for system in get_hostlist():
2131 datadump = get_yaml(system)
2132 ips = [datadump['masterip']]
2133 for ifkey in get_interface_keys(datadump):
2134 ips.append(datadump[ifkey]['ip'].split('/')[0])
2135 print system, ' '.join(ips)
2136 elif sys.argv[2] == "host-pos.txt":
2137 for system in get_hostlist():
2138 datadump = get_yaml(system)
2139 print system, datadump['rdnap_x'], datadump['rdnap_y']
2140 elif sys.argv[2] == 'ssh_config':
2141 print '''
2142Host *.wleiden.net
2143 User root
2144
2145Host 172.16.*.*
2146 User root
2147'''
2148 for system in get_hostlist():
2149 datadump = get_yaml(system)
2150 print '''\
2151Host %s
2152 User root
2153
2154Host %s
2155 User root
2156
2157Host %s
2158 User root
2159
2160Host %s
2161 User root
2162''' % (system, system.lower(), datadump['nodename'], datadump['nodename'].lower())
2163 else:
2164 usage()
2165 else:
2166 usage()
2167 else:
2168 # Do not enable debugging for config requests as it highly clutters the output
2169 if not is_text_request():
2170 cgitb.enable()
2171 response_headers, output = process_cgi_request()
2172 print_cgi_response(response_headers, output)
2173
2174def application(environ, start_response):
2175 status = '200 OK'
2176 response_headers, output = process_cgi_request(environ)
2177 start_response(status, response_headers)
2178
2179 # Debugging only
2180 # output = 'wsgi.multithread = %s' % repr(environ['wsgi.multithread'])
2181 # soutput += '\nwsgi.multiprocess = %s' % repr(environ['wsgi.multiprocess'])
2182 return [output]
2183
2184if __name__ == "__main__":
2185 main()
Note: See TracBrowser for help on using the repository browser.