source: genesis/tools/gformat.py@ 13575

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

Quirk to enable DHCP shared-network again in case an interface has aliases.

Should fix support#65

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