source: genesis/tools/gformat.py@ 13358

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

Gateway is only used if proxy_ileiden is used. Note: incoming RDR services
without proxy functionality will break because of this.

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