source: genesis/tools/gformat.py@ 13598

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

Workaround for USB NIC w/o MAC address.

Some USB dongles do not have an MAC address, defaulting to 00:00:00:00:00:00.
This will cause issues on systems with multiple off them. Workaround is to use
an 'local' MAC adress. To ensure uniqueness use the IP of the the interface as
reference for creating the mac address.

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