source: genesis/tools/gformat.py@ 13551

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

We moved away from hybrid naming prefixes, so no need to generate shortname cnames anymore.

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