source: genesis/tools/gformat.py@ 13403

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

Innitial support for vlan configurations, while here get rid of a) deprecated
ipv4_addrs_ notation and b) make debugging more easy on test runs.

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