source: genesis/tools/gformat.py@ 13564

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

Support spaces in SSID, by using hex encoding, since escaping is a quote-hell.

Fixes RT#65

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