source: genesis/tools/gformat.py@ 13927

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

Fix wrong daemon selection for net4801 board

net4801 also has 256MB RAM, so consider the system equal to the ALIX2 configuration.

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