source: genesis/tools/gformat.py@ 10116

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

Location is super usefull in motd, to see how far away you are connection.

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