source: genesis/tools/gformat.py@ 13617

Last change on this file since 13617 was 13617, checked in by rick, 8 years ago

Fix for vlan(4) and bridge(4) interfaces causing ARP issues.

In a setup with VLAN 103 and 104 on interface re0, we see ARP requests being
duplicated, (unexpected behaviour?).

When interfaces are marked private requests are no duplicated and hence
transmitted back to the correct interface. Since autobridge does not support
parameters, revert back to the more powerfull create_args way.

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