source: genesis/tools/gformat.py@ 13503

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

Major Change: Combine accesspoint interfaces to a shared bridge interfaces
allowing users to roam between accesspoints of local node.

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