source: genesis/tools/gformat.py@ 13565

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

When interface is DHCP client, there is not setting of static interfacing

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