source: genesis/tools/gformat.py@ 13524

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

Quirck to group DHCP interfaces into newly defined bridge0 interface and all
others (which do not have DHCP by definition) are to be set as non autoritive ones.

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