source: genesis/tools/gformat.py@ 10400

Last change on this file since 10400 was 10400, checked in by rick, 13 years ago

Aanpassingen coordinaten in Kaag en Braassem, de WGS84 (degrees) to NAD27
(decimals) conversion is done using:

http://transition.fcc.gov/mb/audio/bickel/DDDMMSS-decimal.html

Reported-by: henk

  • Property svn:executable set to *
  • Property svn:keywords set to Id
File size: 36.0 KB
Line 
1#!/usr/bin/env python
2#
3# vim:ts=2:et:sw=2:ai
4# Wireless Leiden configuration generator, based on yaml files'
5#
6# XXX: This should be rewritten to make use of the ipaddr.py library.
7#
8# Sample apache configuration (mind the AcceptPathInfo!)
9# ScriptAlias /wleiden/config /usr/local/www/genesis/tools/gformat.py
10# <Directory /usr/local/www/genesis>
11# Allow from all
12# AcceptPathInfo On
13# </Directory>
14#
15# Rick van der Zwet <info@rickvanderzwet.nl>
16#
17
18# Hack to make the script directory is also threated as a module search path.
19import sys
20import os
21import re
22sys.path.append(os.path.dirname(__file__))
23
24import cgi
25import cgitb
26import copy
27import glob
28import socket
29import string
30import subprocess
31import time
32import rdnap
33import make_network_kml
34from pprint import pprint
35from collections import defaultdict
36try:
37 import yaml
38except ImportError, e:
39 print e
40 print "[ERROR] Please install the python-yaml or devel/py-yaml package"
41 exit(1)
42
43try:
44 from yaml import CLoader as Loader
45 from yaml import CDumper as Dumper
46except ImportError:
47 from yaml import Loader, Dumper
48
49from jinja2 import Template
50
51import logging
52logging.basicConfig(format='# %(levelname)s: %(message)s' )
53logger = logging.getLogger()
54logger.setLevel(logging.DEBUG)
55
56
57if os.environ.has_key('CONFIGROOT'):
58 NODE_DIR = os.environ['CONFIGROOT']
59else:
60 NODE_DIR = os.path.abspath(os.path.dirname(__file__)) + '/../nodes'
61__version__ = '$Id: gformat.py 10400 2012-04-08 20:22:49Z rick $'
62
63
64files = [
65 'authorized_keys',
66 'dnsmasq.conf',
67 'rc.conf.local',
68 'resolv.conf',
69 'motd',
70 'wleiden.yaml',
71 ]
72
73# Global variables uses
74OK = 10
75DOWN = 20
76UNKNOWN = 90
77
78def get_yaml(item):
79 """ Get configuration yaml for 'item'"""
80 gfile = os.path.join(NODE_DIR,item,'wleiden.yaml')
81
82 f = open(gfile, 'r')
83 datadump = yaml.load(f,Loader=Loader)
84 f.close()
85
86 # Preformat certain needed variables for formatting and push those into special object
87 datadump['autogen_iface_keys'] = get_interface_keys(datadump)
88
89 wlan_count=0
90 for key in datadump['autogen_iface_keys']:
91 if datadump[key]['type'] in ['11a', '11b', '11g', 'wireless']:
92 datadump[key]['autogen_ifname'] = 'wlan%i' % wlan_count
93 wlan_count += 1
94 else:
95 datadump[key]['autogen_ifname'] = datadump[key]['interface'].split(':')[0]
96
97 dhcp_interfaces = [datadump[key]['autogen_ifname'] for key in datadump['autogen_iface_keys'] if datadump[key]['dhcp'] != 'no']
98 datadump['autogen_dhcp_interfaces'] = ' '.join(dhcp_interfaces)
99 datadump['autogen_item'] = item
100
101 datadump['autogen_realname'] = get_realname(datadump)
102 datadump['autogen_domain'] = datadump['domain'] if datadump.has_key('domain') else 'wleiden.net.'
103 datadump['autogen_fqdn'] = datadump['autogen_realname'] + '.' + datadump['autogen_domain']
104 return datadump
105
106
107def store_yaml(datadump, header=False):
108 """ Store configuration yaml for 'item'"""
109 item = datadump['autogen_item']
110 gfile = os.path.join(NODE_DIR,item,'wleiden.yaml')
111
112 f = open(gfile, 'w')
113 f.write(generate_wleiden_yaml(datadump, header))
114 f.close()
115
116
117
118def make_relations():
119 """ Process _ALL_ yaml files to get connection relations """
120 errors = ""
121 poel = defaultdict(list)
122 for host in get_hostlist():
123 try:
124 datadump = get_yaml(host)
125 for iface_key in datadump['autogen_iface_keys']:
126 l = datadump[iface_key]['ip']
127 addr, mask = l.split('/')
128
129 # Not parsing of these folks please
130 if not valid_addr(addr):
131 continue
132
133 addr = parseaddr(addr)
134 mask = int(mask)
135 network = addr & ~((1 << (32 - mask)) - 1)
136 poel[network] += [(host,datadump[iface_key])]
137 except (KeyError, ValueError), e:
138 errors += "[FOUT] in '%s' interface '%s'" % (host,iface_key)
139 errors += e
140 continue
141 return (poel, errors)
142
143
144def get_proxylist():
145 """Get all available proxies proxyX sorting based on X number"""
146 proxylist = sorted([os.path.basename(x) for x in glob.glob("%s/proxy*" % NODE_DIR)],
147 key=lambda name: int(''.join([c for c in name if c in string.digits])),
148 cmp=lambda x,y: x - y) + sorted([os.path.basename(x) for x in glob.glob("%s/Proxy*" % NODE_DIR)])
149 return proxylist
150
151def get_hybridlist():
152 """Get all available proxies hybridX sorting based on X number"""
153 hybridlist = sorted([os.path.basename(x) for x in glob.glob("%s/hybrid*" % NODE_DIR)],
154 key=lambda name: int(''.join([c for c in name if c in string.digits])),
155 cmp=lambda x,y: x - y)
156 return hybridlist
157
158
159def valid_addr(addr):
160 """ Show which address is valid in which are not """
161 return str(addr).startswith('172.')
162
163
164def get_nodelist():
165 """ Get all available nodes - sorted """
166 nodelist = sorted([os.path.basename(x) for x in glob.glob("%s/CNode*" % NODE_DIR)])
167 return nodelist
168
169def get_hostlist():
170 """ Combined hosts and proxy list"""
171 return get_nodelist() + get_proxylist() + get_hybridlist()
172
173def angle_between_points(lat1,lat2,long1,long2):
174 """
175 Return Angle in radians between two GPS coordinates
176 See: http://stackoverflow.com/questions/3809179/angle-between-2-gps-coordinates
177 """
178 dy = lat2 - lat1
179 dx = math.cos(math.pi/180*lat1)*(long2 - long1)
180 angle = math.atan2(dy,dx)
181 return angle
182
183def angle_to_cd(angle):
184 """ Return Dutch Cardinal Direction estimation in 'one digit' of radian angle """
185
186 # For easy conversion get positive degree
187 degrees = math.degrees(angle)
188 if degrees < 0:
189 360 - abs(degrees)
190
191 # Numbers can be confusing calculate from the 4 main directions
192 p = 22.5
193 if degrees < p:
194 return "n"
195 elif degrees < (90 - p):
196 return "no"
197 elif degrees < (90 + p):
198 return "o"
199 elif degrees < (180 - p):
200 return "zo"
201 elif degrees < (180 + p):
202 return "z"
203 elif degrees < (270 - p):
204 return "zw"
205 elif degrees < (270 + p):
206 return "w"
207 elif degrees < (360 - p):
208 return "nw"
209 else:
210 return "n"
211
212
213def generate_title(nodelist):
214 """ Main overview page """
215 items = {'root' : "." }
216 output = """
217<html>
218 <head>
219 <title>Wireless leiden Configurator - GFormat</title>
220 <style type="text/css">
221 th {background-color: #999999}
222 tr:nth-child(odd) {background-color: #cccccc}
223 tr:nth-child(even) {background-color: #ffffff}
224 th, td {padding: 0.1em 1em}
225 </style>
226 </head>
227 <body>
228 <center>
229 <form type="GET" action="%(root)s">
230 <input type="hidden" name="action" value="update">
231 <input type="submit" value="Update Configuration Database (SVN)">
232 </form>
233 <table>
234 <caption><h3>Wireless Leiden Configurator</h3></caption>
235 """ % items
236
237 for node in nodelist:
238 items['node'] = node
239 output += '<tr><td><a href="%(root)s/%(node)s">%(node)s</a></td>' % items
240 for config in files:
241 items['config'] = config
242 output += '<td><a href="%(root)s/%(node)s/%(config)s">%(config)s</a></td>' % items
243 output += "</tr>"
244 output += """
245 </table>
246 <hr />
247 <em>%s</em>
248 </center>
249 </body>
250</html>
251 """ % __version__
252
253 return output
254
255
256
257def generate_node(node):
258 """ Print overview of all files available for node """
259 return "\n".join(files)
260
261def generate_node_overview(host):
262 """ Print overview of all files available for node """
263 datadump = get_yaml(host)
264 params = { 'host' : host }
265 output = "<em><a href='..'>Back to overview</a></em><hr />"
266 output += "<h2>Available files:</h2><ul>"
267 for cf in files:
268 params['cf'] = cf
269 output += '<li><a href="%(host)s/%(cf)s">%(cf)s</a></li>\n' % params
270 output += "</ul>"
271
272 # Generate and connection listing
273 output += "<h2>Connected To:</h2><ul>"
274 (poel, errors) = make_relations()
275 for network, hosts in poel.iteritems():
276 if host in [x[0] for x in hosts]:
277 if len(hosts) == 1:
278 # Single not connected interface
279 continue
280 for remote,ifacedump in hosts:
281 if remote == host:
282 # This side of the interface
283 continue
284 params = { 'remote': remote, 'remote_ip' : ifacedump['ip'] }
285 output += '<li><a href="%(remote)s">%(remote)s</a> -- %(remote_ip)s</li>\n' % params
286 output += "</ul>"
287 output += "<h2>MOTD details:</h2><pre>" + generate_motd(datadump) + "</pre>"
288
289 output += "<hr /><em><a href='..'>Back to overview</a></em>"
290 return output
291
292
293def generate_header(ctag="#"):
294 return """\
295%(ctag)s
296%(ctag)s DO NOT EDIT - Automatically generated by 'gformat'
297%(ctag)s Generated at %(date)s by %(host)s
298%(ctag)s
299""" % { 'ctag' : ctag, 'date' : time.ctime(), 'host' : socket.gethostname() }
300
301
302
303def parseaddr(s):
304 """ Process IPv4 CIDR notation addr to a (binary) number """
305 f = s.split('.')
306 return (long(f[0]) << 24L) + \
307 (long(f[1]) << 16L) + \
308 (long(f[2]) << 8L) + \
309 long(f[3])
310
311
312
313def showaddr(a):
314 """ Display IPv4 addr in (dotted) CIDR notation """
315 return "%d.%d.%d.%d" % ((a >> 24) & 0xff, (a >> 16) & 0xff, (a >> 8) & 0xff, a & 0xff)
316
317
318def is_member(ip, mask, canidate):
319 """ Return True if canidate is part of ip/mask block"""
320 ip_addr = gformat.parseaddr(ip)
321 ip_canidate = gformat.parseaddr(canidate)
322 mask = int(mask)
323 ip_addr = ip_addr & ~((1 << (32 - mask)) - 1)
324 ip_canidate = ip_canidate & ~((1 << (32 - mask)) - 1)
325 return ip_addr == ip_canidate
326
327
328
329
330def netmask2subnet(netmask):
331 """ Given a 'netmask' return corresponding CIDR """
332 return showaddr(0xffffffff & (0xffffffff << (32 - int(netmask))))
333
334
335
336def generate_dnsmasq_conf(datadump):
337 """ Generate configuration file '/usr/local/etc/dnsmasq.conf' """
338 output = generate_header()
339 output += Template("""\
340# DHCP server options
341dhcp-authoritative
342dhcp-fqdn
343domain=dhcp.{{ autogen_fqdn }}
344domain-needed
345expand-hosts
346log-async=100
347
348# Low memory footprint
349cache-size=10000
350
351\n""").render(datadump)
352
353 for iface_key in datadump['autogen_iface_keys']:
354 if not datadump[iface_key].has_key('comment'):
355 datadump[iface_key]['comment'] = None
356 output += "## %(interface)s - %(desc)s - %(comment)s\n" % datadump[iface_key]
357
358 try:
359 (dhcp_start, dhcp_stop) = datadump[iface_key]['dhcp'].split('-')
360 (ip, netmask) = datadump[iface_key]['ip'].split('/')
361 datadump[iface_key]['subnet'] = netmask2subnet(netmask)
362 except (AttributeError, ValueError):
363 output += "# not autoritive\n\n"
364 continue
365
366 dhcp_part = ".".join(ip.split('.')[0:3])
367 datadump[iface_key]['dhcp_start'] = dhcp_part + "." + dhcp_start
368 datadump[iface_key]['dhcp_stop'] = dhcp_part + "." + dhcp_stop
369 output += "dhcp-range=%(interface)s,%(dhcp_start)s,%(dhcp_stop)s,%(subnet)s,24h\n\n" % datadump[iface_key]
370
371 return output
372
373
374
375def generate_rc_conf_local(datadump):
376 """ Generate configuration file '/etc/rc.conf.local' """
377 datadump['autogen_ileiden_enable'] = 'yes' if datadump['ileiden'] else 'no'
378
379 ileiden_proxies = []
380 normal_proxies = []
381 for proxy in get_proxylist():
382 proxydump = get_yaml(proxy)
383 if proxydump['ileiden']:
384 ileiden_proxies.append(proxydump)
385 else:
386 normal_proxies.append(proxydump)
387 datadump['autogen_ileiden_proxies'] = ','.join([x['masterip'] for x in ileiden_proxies])
388 datadump['autogen_ileiden_proxies_names'] = ','.join([x['autogen_item'] for x in ileiden_proxies])
389 datadump['autogen_normal_proxies'] = ','.join([x['masterip'] for x in normal_proxies])
390 datadump['autogen_normal_proxies_names'] = ','.join([x['autogen_item'] for x in normal_proxies])
391
392 output = generate_header("#");
393 output += Template("""\
394hostname='{{ autogen_fqdn }}'
395location='{{ location }}'
396nodetype="{{ nodetype }}"
397
398{% if tproxy -%}
399tproxy_enable='YES'
400tproxy_range='{{ tproxy }}'
401{% else -%}
402tproxy_enable='NO'
403{% endif -%}
404
405{% if nodetype == "Proxy" or nodetype == "Hybrid" %}
406#
407# Edge Configuration
408#
409
410
411# Firewall and Routing Configuration
412
413{% if gateway -%}
414defaultrouter="{{ gateway }}"
415{% else -%}
416#defaultrouter="NOTSET"
417{% endif -%}
418internalif="{{ internalif }}"
419ileiden_enable="{{ autogen_ileiden_enable }}"
420gateway_enable="{{ autogen_ileiden_enable }}"
421pf_enable="yes"
422pf_rules="/etc/pf.conf"
423{% if autogen_ileiden_enable == "yes" -%}
424pf_flags="-D ext_if={{ externalif }} -D int_if={{ internalif }} -D publicnat={80,443}"
425lvrouted_enable="{{ autogen_ileiden_enable }}"
426lvrouted_flags="-u -s s00p3rs3kr3t -m 28"
427{% else -%}
428pf_flags="-D ext_if={{ externalif }} -D int_if={{ internalif }} -D publicnat={0}"
429{% endif -%}
430{% if internalroute -%}
431static_routes="wleiden"
432route_wleiden="-net 172.16.0.0/12 {{ internalroute }}"
433{% endif -%}
434{% endif -%}
435
436{% if nodetype == "CNode" %}
437#
438# NODE iLeiden Configuration
439#
440# iLeiden Proxies {{ autogen_ileiden_proxies_names }}
441list_ileiden_proxies="{{ autogen_ileiden_proxies }}"
442# normal Proxies {{ autogen_normal_proxies_names }}
443list_normal_proxies="{{ autogen_normal_proxies }}"
444
445lvrouted_flags="-u -s s00p3rs3kr3t -m 28 -z $list_ileiden_proxies"
446{% endif %}
447{% if vpnif -%}
448 vpnif="{{ vpnif }}"
449{% endif -%}
450
451captive_portal_whitelist=""
452captive_portal_interfaces="{{ autogen_dhcp_interfaces }}"
453\n
454""").render(datadump)
455
456 # lo0 configuration:
457 # - 172.32.255.1/32 is the proxy.wleiden.net deflector
458 # - masterip is special as it needs to be assigned to at
459 # least one interface, so if not used assign to lo0
460 addrs_list = { 'lo0' : [("127.0.0.1/8", "LocalHost"), ("172.31.255.1/32","Proxy IP")] }
461 iface_map = {'lo0' : 'lo0'}
462 dhclient_if = {'lo0' : False}
463
464 masterip_used = False
465 for iface_key in datadump['autogen_iface_keys']:
466 if datadump[iface_key]['ip'].startswith(datadump['masterip']):
467 masterip_used = True
468 break
469 if not masterip_used:
470 addrs_list['lo0'].append((datadump['masterip'] + "/32", 'Master IP Not used in interface'))
471
472 for iface_key in datadump['autogen_iface_keys']:
473 ifacedump = datadump[iface_key]
474 ifname = ifacedump['autogen_ifname']
475
476 # Flag dhclient is possible
477 dhclient_if[ifname] = ifacedump.has_key('dhcpclient') and ifacedump['dhcpclient']
478
479 # Add interface IP to list
480 item = (ifacedump['ip'], ifacedump['desc'])
481 if addrs_list.has_key(ifname):
482 addrs_list[ifname].append(item)
483 else:
484 addrs_list[ifname] = [item]
485
486 # Alias only needs IP assignment for now, this might change if we
487 # are going to use virtual accesspoints
488 if "alias" in iface_key:
489 continue
490
491 # XXX: Might want to deduct type directly from interface name
492 if ifacedump['type'] in ['11a', '11b', '11g', 'wireless']:
493 # Default to station (client) mode
494 ifacedump['wlanmode'] = "sta"
495 if ifacedump['mode'] in ['master', 'master-wds', 'ap', 'ap-wds']:
496 ifacedump['wlanmode'] = "ap"
497 # Default to 802.11b mode
498 ifacedump['mode'] = '11b'
499 if ifacedump['type'] in ['11a', '11b' '11g']:
500 ifacedump['mode'] = ifacedump['type']
501
502 if not ifacedump.has_key('channel'):
503 if ifacedump['type'] == '11a':
504 ifacedump['channel'] = 36
505 else:
506 ifacedump['channel'] = 1
507
508 # Allow special hacks at the back like wds and stuff
509 if not ifacedump.has_key('extra'):
510 ifacedump['extra'] = 'regdomain ETSI country NL'
511
512 output += "wlans_%(interface)s='%(autogen_ifname)s'\n" % ifacedump
513 output += ("create_args_%(autogen_ifname)s='wlanmode %(wlanmode)s mode " +\
514 "%(mode)s ssid %(ssid)s %(extra)s channel %(channel)s'\n") % ifacedump
515
516 elif ifacedump['type'] in ['ethernet', 'eth']:
517 # No special config needed besides IP
518 pass
519 else:
520 assert False, "Unknown type " + ifacedump['type']
521
522 # Print IP address which needs to be assigned over here
523 output += "\n"
524 for iface,addrs in sorted(addrs_list.iteritems()):
525 for addr, comment in sorted(addrs,key=lambda x: parseaddr(x[0].split('/')[0])):
526 output += "# %s || %s || %s\n" % (iface, addr, comment)
527
528 # Write DHCLIENT entry
529 if dhclient_if[iface]:
530 output += "ifconfig_%s='SYNCDHCP'\n\n" % (iface)
531 else:
532 output += "ipv4_addrs_%s='%s'\n\n" % (iface, " ".join([x[0] for x in addrs]))
533
534 return output
535
536
537
538
539def get_all_configs():
540 """ Get dict with key 'host' with all configs present """
541 configs = dict()
542 for host in get_hostlist():
543 datadump = get_yaml(host)
544 configs[host] = datadump
545 return configs
546
547
548def get_interface_keys(config):
549 """ Quick hack to get all interface keys, later stage convert this to a iterator """
550 return sorted([elem for elem in config.keys() if (elem.startswith('iface_') and not "lo0" in elem)])
551
552
553def get_used_ips(configs):
554 """ Return array of all IPs used in config files"""
555 ip_list = []
556 for config in configs:
557 ip_list.append(config['masterip'])
558 for iface_key in get_interface_keys(config):
559 l = config[iface_key]['ip']
560 addr, mask = l.split('/')
561 # Special case do not process
562 if valid_addr(addr):
563 ip_list.append(addr)
564 else:
565 logger.error("## IP '%s' in '%s' not valid" % (addr, config['nodename']))
566 return sorted(ip_list)
567
568
569
570def generate_resolv_conf(datadump):
571 """ Generate configuration file '/etc/resolv.conf' """
572 output = generate_header("#");
573 output += """\
574search wleiden.net
575"""
576 if datadump['nodetype'] == 'Proxy':
577 output += """\
578# Try local (cache) first
579nameserver 127.0.0.1
580nameserver 8.8.8.8 # Google Public NameServer
581nameserver 8.8.4.4 # Google Public NameServer
582"""
583 elif datadump['nodetype'] == 'Hybrid':
584 for proxy in get_proxylist():
585 proxy_ip = get_yaml(proxy)['masterip']
586 output += "nameserver %-15s # %s\n" % (proxy_ip, proxy)
587 output += """\
588nameserver 8.8.8.8 # Google Public NameServer
589nameserver 8.8.4.4 # Google Public NameServer
590"""
591 else:
592 output += """\
593# Try local (cache) first
594nameserver 127.0.0.1
595
596# Proxies are recursive nameservers
597# needs to be in resolv.conf for dnsmasq as well
598""" % datadump
599 for proxy in get_proxylist():
600 proxy_ip = get_yaml(proxy)['masterip']
601 output += "nameserver %-15s # %s\n" % (proxy_ip, proxy)
602
603 return output
604
605def generate_motd(datadump):
606 """ Generate configuration file '/etc/motd' """
607 output = """\
608FreeBSD 9.0-RELEASE (kernel.wleiden) #0 r230587: Sun Jan 29 17:09:57 CET 2012
609
610 WWW: %(autogen_fqdn)s - http://www.wirelessleiden.nl
611 Loc: %(location)s
612
613Interlinks:
614""" % datadump
615
616 # XXX: This is a hacky way to get the required data
617 for line in generate_rc_conf_local(datadump).split('\n'):
618 if '||' in line and not line[1:].split()[0] in ['lo0', 'ath0'] :
619 output += " - %s \n" % line[1:]
620 output += """\
621Attached bridges:
622"""
623 for iface_key in datadump['autogen_iface_keys']:
624 ifacedump = datadump[iface_key]
625 if ifacedump.has_key('ns_ip'):
626 output += " - %(interface)s || %(mode)s || %(ns_ip)s\n" % ifacedump
627
628 return output
629
630
631def format_yaml_value(value):
632 """ Get yaml value in right syntax for outputting """
633 if isinstance(value,str):
634 output = '"%s"' % value
635 else:
636 output = value
637 return output
638
639
640
641def format_wleiden_yaml(datadump):
642 """ Special formatting to ensure it is editable"""
643 output = "# Genesis config yaml style\n"
644 output += "# vim:ts=2:et:sw=2:ai\n"
645 output += "#\n"
646 iface_keys = [elem for elem in datadump.keys() if elem.startswith('iface_')]
647 for key in sorted(set(datadump.keys()) - set(iface_keys)):
648 output += "%-10s: %s\n" % (key, format_yaml_value(datadump[key]))
649
650 output += "\n\n"
651
652 key_order = [ 'comment', 'interface', 'ip', 'desc', 'sdesc', 'mode', 'type',
653 'extra_type', 'channel', 'ssid', 'dhcp' ]
654
655 for iface_key in sorted(iface_keys):
656 output += "%s:\n" % iface_key
657 for key in key_order + list(sorted(set(datadump[iface_key].keys()) - set(key_order))):
658 if datadump[iface_key].has_key(key):
659 output += " %-11s: %s\n" % (key, format_yaml_value(datadump[iface_key][key]))
660 output += "\n\n"
661
662 return output
663
664
665
666def generate_wleiden_yaml(datadump, header=True):
667 """ Generate (petty) version of wleiden.yaml"""
668 for key in datadump.keys():
669 if key.startswith('autogen_'):
670 del datadump[key]
671 # Interface autogen cleanups
672 elif type(datadump[key]) == dict:
673 for key2 in datadump[key].keys():
674 if key2.startswith('autogen_'):
675 del datadump[key][key2]
676
677 output = generate_header("#") if header else ''
678 output += format_wleiden_yaml(datadump)
679 return output
680
681
682def generate_yaml(datadump):
683 return generate_config(datadump['nodename'], "wleiden.yaml", datadump)
684
685
686
687def generate_config(node, config, datadump=None):
688 """ Print configuration file 'config' of 'node' """
689 output = ""
690 try:
691 # Load config file
692 if datadump == None:
693 datadump = get_yaml(node)
694
695 if config == 'wleiden.yaml':
696 output += generate_wleiden_yaml(datadump)
697 elif config == 'authorized_keys':
698 f = open(os.path.join(NODE_DIR,"global_keys"), 'r')
699 output += f.read()
700 f.close()
701 elif config == 'dnsmasq.conf':
702 output += generate_dnsmasq_conf(datadump)
703 elif config == 'rc.conf.local':
704 output += generate_rc_conf_local(datadump)
705 elif config == 'resolv.conf':
706 output += generate_resolv_conf(datadump)
707 elif config == 'motd':
708 output += generate_motd(datadump)
709 else:
710 assert False, "Config not found!"
711 except IOError, e:
712 output += "[ERROR] Config file not found"
713 return output
714
715
716
717def process_cgi_request():
718 """ When calling from CGI """
719 # Update repository if requested
720 form = cgi.FieldStorage()
721 if form.getvalue("action") == "update":
722 print "Refresh: 5; url=."
723 print "Content-type:text/plain\r\n\r\n",
724 print "[INFO] Updating subverion, please wait..."
725 print subprocess.Popen(['svn', 'cleanup', "%s/.." % NODE_DIR], stderr=subprocess.STDOUT, stdout=subprocess.PIPE).communicate()[0],
726 print subprocess.Popen(['svn', 'up', "%s/.." % NODE_DIR], stderr=subprocess.STDOUT, stdout=subprocess.PIPE).communicate()[0],
727 print "[INFO] All done, redirecting in 5 seconds"
728 sys.exit(0)
729
730
731 base_uri = os.environ['PATH_INFO']
732 uri = base_uri.strip('/').split('/')
733
734 output = ""
735 if base_uri.endswith('/create/network.kml'):
736 output += "Content-type:application/vnd.google-earth.kml+xml\r\n\r\n"
737 output += make_network_kml.make_graph()
738 elif not uri[0]:
739 if is_text_request():
740 output += "Content-type:text/plain\r\n\r\n"
741 output += '\n'.join(get_hostlist())
742 else:
743 output += "Content-type:text/html\r\n\r\n"
744 output += generate_title(get_hostlist())
745 elif len(uri) == 1:
746 if is_text_request():
747 output += "Content-type:text/plain\r\n\r\n"
748 output += generate_node(uri[0])
749 else:
750 output += "Content-type:text/html\r\n\r\n"
751 output += generate_node_overview(uri[0])
752 elif len(uri) == 2:
753 output += "Content-type:text/plain\r\n\r\n"
754 output += generate_config(uri[0], uri[1])
755 else:
756 assert False, "Invalid option"
757 print output
758
759def get_realname(datadump):
760 # Proxy naming convention is special, as the proxy name is also included in
761 # the nodename, when it comes to the numbered proxies.
762 if datadump['nodetype'] == 'Proxy':
763 realname = datadump['nodetype'] + datadump['nodename'].replace('proxy','')
764 else:
765 # By default the full name is listed and also a shortname CNAME for easy use.
766 realname = datadump['nodetype'] + datadump['nodename']
767 return(realname)
768
769
770
771def make_dns(output_dir = 'dns', external = False):
772 items = dict()
773
774 # hostname is key, IP is value
775 wleiden_zone = dict()
776 wleiden_cname = dict()
777
778 pool = dict()
779 for node in get_hostlist():
780 logger.info("Processing host %s", node)
781 datadump = get_yaml(node)
782
783 # Proxy naming convention is special
784 fqdn = datadump['autogen_realname']
785 if datadump['nodetype'] == 'CNode':
786 wleiden_cname[datadump['nodename']] = fqdn
787
788 wleiden_zone[fqdn] = datadump['masterip']
789
790 # Hacking to get proper DHCP IPs and hostnames
791 for iface_key in get_interface_keys(datadump):
792 iface_name = datadump[iface_key]['interface'].replace(':',"-alias-")
793 (ip, netmask) = datadump[iface_key]['ip'].split('/')
794 try:
795 (dhcp_start, dhcp_stop) = datadump[iface_key]['dhcp'].split('-')
796 datadump[iface_key]['subnet'] = netmask2subnet(netmask)
797 dhcp_part = ".".join(ip.split('.')[0:3])
798 if ip != datadump['masterip']:
799 wleiden_zone["dhcp-gateway-%s.%s" % (iface_name, fqdn)] = ip
800 for i in range(int(dhcp_start), int(dhcp_stop) + 1):
801 wleiden_zone["dhcp-%s-%s.%s" % (i, iface_name, fqdn)] = "%s.%s" % (dhcp_part, i)
802 except (AttributeError, ValueError):
803 # First push it into a pool, to indentify the counter-part later on
804 addr = parseaddr(ip)
805 netmask = int(netmask)
806 addr = addr & ~((1 << (32 - netmask)) - 1)
807 if pool.has_key(addr):
808 pool[addr] += [(iface_name, fqdn, ip)]
809 else:
810 pool[addr] = [(iface_name, fqdn, ip)]
811 continue
812
813
814 def pool_to_name(node, pool_members):
815 """Convert the joined name to a usable pool name"""
816
817 # Get rid of the own entry
818 pool_members = list(set(pool_members) - set([fqdn]))
819
820 target = oldname = ''
821 for node in sorted(pool_members):
822 (name, number) = re.match('^([A-Za-z]+)([0-9]*)$',node).group(1,2)
823 target += "-" + number if name == oldname else "-" + node if target else node
824 oldname = name
825
826 return target
827
828
829 # WL uses an /29 to configure an interface. IP's are ordered like this:
830 # MasterA (.1) -- DeviceA (.2) <<>> DeviceB (.3) --- SlaveB (.4)
831
832 sn = lambda x: re.sub(r'(?i)^cnode','',x)
833
834 # Automatic naming convention of interlinks namely 2 + remote.lower()
835 for (key,value) in pool.iteritems():
836 # Make sure they are sorted from low-ip to high-ip
837 value = sorted(value, key=lambda x: parseaddr(x[2]))
838
839 if len(value) == 1:
840 (iface_name, fqdn, ip) = value[0]
841 wleiden_zone["2unused-%s.%s" % (iface_name, fqdn)] = ip
842
843 # Device DNS names
844 if 'cnode' in fqdn.lower():
845 wleiden_zone["d-at-%s.%s" % (iface_name, fqdn)] = showaddr(parseaddr(ip) + 1)
846 wleiden_cname["d-at-%s.%s" % (iface_name,sn(fqdn))] = "d-at-%s.%s" % (iface_name, fqdn)
847
848 elif len(value) == 2:
849 (a_iface_name, a_fqdn, a_ip) = value[0]
850 (b_iface_name, b_fqdn, b_ip) = value[1]
851 wleiden_zone["2%s.%s" % (b_fqdn,a_fqdn)] = a_ip
852 wleiden_zone["2%s.%s" % (a_fqdn,b_fqdn)] = b_ip
853
854 # Device DNS names
855 if 'cnode' in a_fqdn.lower() and 'cnode' in b_fqdn.lower():
856 wleiden_zone["d-at-%s.%s" % (a_iface_name, a_fqdn)] = showaddr(parseaddr(a_ip) + 1)
857 wleiden_zone["d-at-%s.%s" % (b_iface_name, b_fqdn)] = showaddr(parseaddr(b_ip) - 1)
858 wleiden_cname["d-at-%s.%s" % (a_iface_name,sn(a_fqdn))] = "d-at-%s.%s" % (a_iface_name, a_fqdn)
859 wleiden_cname["d-at-%s.%s" % (b_iface_name,sn(b_fqdn))] = "d-at-%s.%s" % (b_iface_name, b_fqdn)
860 wleiden_cname["d2%s.%s" % (sn(b_fqdn),sn(a_fqdn))] = "d-at-%s.%s" % (a_iface_name, a_fqdn)
861 wleiden_cname["d2%s.%s" % (sn(a_fqdn),sn(b_fqdn))] = "d-at-%s.%s" % (b_iface_name, b_fqdn)
862
863 else:
864 pool_members = [k[1] for k in value]
865 for item in value:
866 (iface_name, fqdn, ip) = item
867 pool_name = "2pool-" + showaddr(key).replace('.','-') + "-" + pool_to_name(fqdn,pool_members)
868 wleiden_zone["%s.%s" % (pool_name, fqdn)] = ip
869
870 # Include static DNS entries
871 # XXX: Should they override the autogenerated results?
872 # XXX: Convert input to yaml more useable.
873 # Format:
874 ##; this is a comment
875 ## roomburgh=CNodeRoomburgh1
876 ## apkerk1.CNodeVosko=172.17.176.8 ;this as well
877 dns = yaml.load(open(os.path.join(NODE_DIR,'../dns/staticDNS.yaml'),'r'))
878
879 # Hack to allow special entries, for development
880 wleiden_raw = dns['raw']
881 del dns['raw']
882
883 for comment, block in dns.iteritems():
884 for k,v in block.iteritems():
885 if valid_addr(v):
886 wleiden_zone[k] = v
887 else:
888 wleiden_cname[k] = v
889
890 details = dict()
891 # 24 updates a day allowed
892 details['serial'] = time.strftime('%Y%m%d%H')
893
894 if external:
895 dns_masters = ['siteview.wirelessleiden.nl', 'ns1.vanderzwet.net']
896 else:
897 dns_masters = ['sunny.wleiden.net']
898
899 details['master'] = dns_masters[0]
900 details['ns_servers'] = '\n'.join(['\tNS\t%s.' % x for x in dns_masters])
901
902 dns_header = '''
903$TTL 3h
904%(zone)s. SOA %(master)s. beheer.lijst.wirelessleiden.nl. ( %(serial)s 1d 12h 1w 3h )
905 ; Serial, Refresh, Retry, Expire, Neg. cache TTL
906
907%(ns_servers)s
908 \n'''
909
910
911 if not os.path.isdir(output_dir):
912 os.makedirs(output_dir)
913 details['zone'] = 'wleiden.net'
914 f = open(os.path.join(output_dir,"db." + details['zone']), "w")
915 f.write(dns_header % details)
916
917 for host,ip in wleiden_zone.iteritems():
918 if valid_addr(ip):
919 f.write("%s.wleiden.net. IN A %s \n" % (host.lower(), ip))
920 for source,dest in wleiden_cname.iteritems():
921 f.write("%s.wleiden.net. IN CNAME %s.wleiden.net.\n" % (source.lower(), dest.lower()))
922 for source, dest in wleiden_raw.iteritems():
923 f.write("%s.wleiden.net. %s\n" % (source, dest))
924 f.close()
925
926 # Create whole bunch of specific sub arpa zones. To keep it compliant
927 for s in range(16,32):
928 details['zone'] = '%i.172.in-addr.arpa' % s
929 f = open(os.path.join(output_dir,"db." + details['zone']), "w")
930 f.write(dns_header % details)
931
932 #XXX: Not effient, fix to proper data structure and do checks at other
933 # stages
934 for host,ip in wleiden_zone.iteritems():
935 if valid_addr(ip):
936 if int(ip.split('.')[1]) == s:
937 rev_ip = '.'.join(reversed(ip.split('.')))
938 f.write("%s.in-addr.arpa. IN PTR %s.wleiden.net.\n" % (rev_ip.lower(), host.lower()))
939 f.close()
940
941
942def usage():
943 print """Usage: %s <standalone [port] |test [test arguments]|static|dns>
944Examples:
945\tdns [outputdir] = Generate BIND compliant zone files in dns.
946\tstandalone = Run configurator webserver [default port=8000]
947\twind-export = Generate SQL import scripts for WIND database
948\tfull-export = Generate yaml export script for heatmap.
949\tstatic = Generate all config files and store on disk
950\t with format ./static/%%NODE%%/%%FILE%%
951\ttest CNodeRick dnsmasq.conf = Receive output of CGI script
952\t for arguments CNodeRick/dnsmasq.conf
953\tlist <all|nodes|proxies> = List systems which marked up.
954"""
955 exit(0)
956
957
958def is_text_request():
959 """ Find out whether we are calling from the CLI or any text based CLI utility """
960 try:
961 return os.environ['HTTP_USER_AGENT'].split()[0] in ['curl', 'fetch', 'wget']
962 except KeyError:
963 return True
964
965def main():
966 """Hard working sub"""
967 # Allow easy hacking using the CLI
968 if not os.environ.has_key('PATH_INFO'):
969 if len(sys.argv) < 2:
970 usage()
971
972 if sys.argv[1] == "standalone":
973 import SocketServer
974 import CGIHTTPServer
975 # Hop to the right working directory.
976 os.chdir(os.path.dirname(__file__))
977 try:
978 PORT = int(sys.argv[2])
979 except (IndexError,ValueError):
980 PORT = 8000
981
982 class MyCGIHTTPRequestHandler(CGIHTTPServer.CGIHTTPRequestHandler):
983 """ Serve this CGI from the root of the webserver """
984 def is_cgi(self):
985 if "favicon" in self.path:
986 return False
987
988 self.cgi_info = (os.path.basename(__file__), self.path)
989 self.path = ''
990 return True
991 handler = MyCGIHTTPRequestHandler
992 SocketServer.TCPServer.allow_reuse_address = True
993 httpd = SocketServer.TCPServer(("", PORT), handler)
994 httpd.server_name = 'localhost'
995 httpd.server_port = PORT
996
997 logger.info("serving at port %s", PORT)
998 try:
999 httpd.serve_forever()
1000 except KeyboardInterrupt:
1001 httpd.shutdown()
1002 logger.info("All done goodbye")
1003 elif sys.argv[1] == "test":
1004 os.environ['PATH_INFO'] = "/".join(sys.argv[2:])
1005 os.environ['SCRIPT_NAME'] = __file__
1006 process_cgi_request()
1007 elif sys.argv[1] == "unit-test":
1008 os.environ['SCRIPT_NAME'] = __file__
1009 for host in get_hostlist():
1010 for outfile in files:
1011 os.environ['PATH_INFO'] = "/".join([host,outfile])
1012 try:
1013 process_cgi_request()
1014 except Exception:
1015 print "# ERROR: %s" % os.environ['PATH_INFO']
1016 raise
1017
1018
1019 elif sys.argv[1] == "static":
1020 items = dict()
1021 for node in get_hostlist():
1022 items['node'] = node
1023 items['wdir'] = "./static/%(node)s" % items
1024 if not os.path.isdir(items['wdir']):
1025 os.makedirs(items['wdir'])
1026 datadump = get_yaml(node)
1027 for config in files:
1028 items['config'] = config
1029 logger.info("## Generating %(node)s %(config)s" % items)
1030 f = open("%(wdir)s/%(config)s" % items, "w")
1031 f.write(generate_config(node, config, datadump))
1032 f.close()
1033 elif sys.argv[1] == "wind-export":
1034 items = dict()
1035 for node in get_hostlist():
1036 datadump = get_yaml(node)
1037 sql = """INSERT IGNORE INTO nodes (name, name_ns, longitude, latitude)
1038 VALUES ('%(nodename)s', '%(nodename)s', %(latitude)s, %(longitude)s);""" % datadump;
1039 sql = """INSERT IGNORE INTO users_nodes (user_id, node_id, owner)
1040 VALUES (
1041 (SELECT id FROM users WHERE username = 'rvdzwet'),
1042 (SELECT id FROM nodes WHERE name = '%(nodename)s'),
1043 'Y');""" % datadump
1044 #for config in files:
1045 # items['config'] = config
1046 # print "## Generating %(node)s %(config)s" % items
1047 # f = open("%(wdir)s/%(config)s" % items, "w")
1048 # f.write(generate_config(node, config, datadump))
1049 # f.close()
1050 for node in get_hostlist():
1051 datadump = get_yaml(node)
1052 for iface_key in sorted([elem for elem in datadump.keys() if elem.startswith('iface_')]):
1053 ifacedump = datadump[iface_key]
1054 if ifacedump.has_key('mode') and ifacedump['mode'] == 'ap-wds':
1055 ifacedump['nodename'] = datadump['nodename']
1056 if not ifacedump.has_key('channel') or not ifacedump['channel']:
1057 ifacedump['channel'] = 0
1058 sql = """INSERT INTO links (node_id, type, ssid, protocol, channel, status)
1059 VALUES ((SELECT id FROM nodes WHERE name = '%(nodename)s'), 'ap',
1060 '%(ssid)s', 'IEEE 802.11b', %(channel)s, 'active');""" % ifacedump
1061 elif sys.argv[1] == "full-export":
1062 hosts = {}
1063 for node in get_hostlist():
1064 datadump = get_yaml(node)
1065 hosts[datadump['nodename']] = datadump
1066 print yaml.dump(hosts)
1067
1068 elif sys.argv[1] == "dns":
1069 make_dns(sys.argv[2] if len(sys.argv) > 2 else 'dns', 'external' in sys.argv)
1070 elif sys.argv[1] == "cleanup":
1071 # First generate all datadumps
1072 datadumps = dict()
1073 for host in get_hostlist():
1074 logger.info("# Processing: %s", host)
1075 datadump = get_yaml(host)
1076 datadumps[datadump['autogen_realname']] = datadump
1077
1078 for host,datadump in datadumps.iteritems():
1079 if datadump['rdnap_x'] and datadump['rdnap_y']:
1080 datadump['latitude'], datadump['longitude'] = rdnap.rd2etrs(datadump['rdnap_x'], datadump['rdnap_y'])
1081 elif datadump['latitude'] and datadump['longitude']:
1082 datadump['rdnap_x'], datadump['rdnap_y'] = rdnap.etrs2rd(datadump['latitude'], datadump['longitude'])
1083
1084 if datadump['nodename'].startswith('Proxy'):
1085 datadump['nodename'] = datadump['nodename'].lower()
1086
1087 for iface_key in datadump['autogen_iface_keys']:
1088 # Wireless Leiden SSID have an consistent lowercase/uppercase
1089 if datadump[iface_key].has_key('ssid'):
1090 ssid = datadump[iface_key]['ssid']
1091 prefix = 'ap-WirelessLeiden-'
1092 if ssid.lower().startswith(prefix.lower()):
1093 datadump[iface_key]['ssid'] = prefix + ssid[len(prefix)].upper() + ssid[len(prefix) + 1:]
1094 if datadump[iface_key].has_key('ns_ip') and not datadump[iface_key].has_key('mode'):
1095 datadump[iface_key]['mode'] = 'autogen-FIXME'
1096 if not datadump[iface_key].has_key('desc'):
1097 datadump[iface_key]['desc'] = 'autogen-FIXME'
1098 store_yaml(datadump)
1099 elif sys.argv[1] == "list":
1100 if sys.argv[2] == "nodes":
1101 systems = get_nodelist()
1102 elif sys.argv[2] == "proxies":
1103 systems = get_proxylist()
1104 elif sys.argv[2] == "all":
1105 systems = get_hostlist()
1106 else:
1107 usage()
1108 for system in systems:
1109 datadump = get_yaml(system)
1110 if datadump['status'] == "up":
1111 print system
1112 elif sys.argv[1] == "create":
1113 if sys.argv[2] == "network.kml":
1114 print make_network_kml.make_graph()
1115 else:
1116 usage()
1117 usage()
1118 else:
1119 # Do not enable debugging for config requests as it highly clutters the output
1120 if not is_text_request():
1121 cgitb.enable()
1122 process_cgi_request()
1123
1124
1125if __name__ == "__main__":
1126 main()
Note: See TracBrowser for help on using the repository browser.