blob: 8b630ecfd332e2fe8162765641af46b43b1226c8 [file] [log] [blame]
Max1f6a9ba2016-08-02 16:54:55 +02001#!/usr/bin/python2
2
3mod_license = '''
4/*
5 * Copyright (C) 2016 sysmocom s.f.m.c. GmbH
6 *
7 * All Rights Reserved
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 3 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License along
20 * with this program; if not, write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22 */
23'''
24
25import sys, argparse, random, logging, tornado.ioloop, tornado.web, tornado.tcpclient, tornado.httpclient, eventsource, bsc_control
26from eventsource import listener, request
27
28'''
29N. B: this is not an example of building proper REST API or building secure web application.
30It's only purpose is to illustrate conversion of Osmocom's Control Interface to web-friendly API.
31Exposing this to Internet while connected to production network might lead to all sorts of mischief and mayhem
32from NSA' TAO breaking into your network to zombie apocalypse. Do NOT do that.
33'''
34
35token = None
36stream = None
37url = None
38
39'''
40Returns json according to following schema - see http://json-schema.org/documentation.html for details:
41{
42 "title": "Ctrl Schema",
43 "type": "object",
44 "properties": {
45 "variable": {
46 "type": "string"
47 },
48 "varlue": {
49 "type": "string"
50 }
51 },
52 "required": ["interface", "variable", "value"]
53}
54Example validation from command-line:
55json validate --schema-file=schema.json --document-file=data.json
56The interface is represented as string because it might look different for IPv4 vs v6.
57'''
58
59def read_header(data):
60 t_length = bsc_control.ipa_ctrl_header(data)
61 if (t_length):
62 stream.read_bytes(t_length - 1, callback = read_trap)
63 else:
64 print >> sys.stderr, "protocol error: length missing in %s!" % data
65
66@tornado.gen.coroutine
67def read_trap(data):
68 (t, z, v, p) = data.split()
69 if (t != 'TRAP' or int(z) != 0):
70 print >> sys.stderr, "protocol error: TRAP != %s or 0! = %d" % (t, int(z))
71 else:
72 yield tornado.httpclient.AsyncHTTPClient().fetch(tornado.httpclient.HTTPRequest(url = "%s/%s/%s" % (url, "ping", token),
73 method = 'POST',
74 headers = {'Content-Type': 'application/json'},
75 body = tornado.escape.json_encode({ 'variable' : v, 'value' : p })))
76 stream.read_bytes(4, callback = read_header)
77
78@tornado.gen.coroutine
79def trap_setup(host, port, target_host, target_port, tk):
80 global stream
81 global url
82 global token
83 token = tk
84 url = "http://%s:%s/sse" % (host, port)
85 stream = yield tornado.tcpclient.TCPClient().connect(target_host, target_port)
86 stream.read_bytes(4, callback = read_header)
87
88def get_v(s, v):
89 return { 'variable' : v, 'value' : bsc_control.get_var(s, tornado.escape.native_str(v)) }
90
91class CtrlHandler(tornado.web.RequestHandler):
92 def initialize(self):
93 self.skt = bsc_control.connect(self.settings['ctrl_host'], self.settings['ctrl_port'])
94
95 def get(self, v):
96 self.write(get_v(self.skt, v))
97
98 def post(self):
99 self.write(get_v(self.skt, self.get_argument("variable")))
100
101class SetCtrl(CtrlHandler):
102 def get(self, var, val):
103 bsc_control.set_var(self.skt, tornado.escape.native_str(var), tornado.escape.native_str(val))
104 super(SetCtrl, self).get(tornado.escape.native_str(var))
105
106 def post(self):
107 bsc_control.set_var(self.skt, tornado.escape.native_str(self.get_argument("variable")), tornado.escape.native_str(self.get_argument("value")))
108 super(SetCtrl, self).post()
109
110class Slash(tornado.web.RequestHandler):
111 def get(self):
112 self.write('<html><head><title>%s</title></head><body>Using Tornado framework v%s'
113 '<form action="/get" method="POST">'
114 '<input type="text" name="variable">'
115 '<input type="submit" value="GET">'
116 '</form>'
117 '<form action="/set" method="POST">'
118 '<input type="text" name="variable">'
119 '<input type="text" name="value">'
120 '<input type="submit" value="SET">'
121 '</form>'
122 '</body></html>' % ("Osmocom Control Interface Proxy", tornado.version))
123
124if __name__ == '__main__':
125 p = argparse.ArgumentParser(description='Osmocom Control Interface proxy.')
126 p.add_argument('-c', '--control-port', type = int, default = 4252, help = "Target Control Interface port")
127 p.add_argument('-a', '--control-host', default = 'localhost', help = "Target Control Interface adress")
128 p.add_argument('-b', '--host', default = 'localhost', help = "Adress to bind proxy's web interface")
129 p.add_argument('-p', '--port', type = int, default = 6969, help = "Port to bind proxy's web interface")
130 p.add_argument('-d', '--debug', action='store_true', help = "Activate debugging (default off)")
131 p.add_argument('-t', '--token', default = 'osmocom', help = "Token to be used by SSE client in URL e. g. http://127.0.0.1:8888/poll/osmocom where 'osmocom' is default token value")
132 p.add_argument('-k', '--keepalive', type = int, default = 5000, help = "Timeout betwwen keepalive messages, in milliseconds, defaults to 5000")
133 args = p.parse_args()
134 random.seed()
135 tornado.netutil.Resolver.configure('tornado.netutil.ThreadedResolver') # Use non-blocking resolver
136 logging.basicConfig()
137 application = tornado.web.Application([
138 (r"/", Slash),
139 (r"/get", CtrlHandler),
140 (r"/get/(.*)", CtrlHandler),
141 (r"/set", SetCtrl),
142 (r"/set/(.*)/(.*)", SetCtrl),
143 (r"/sse/(.*)/(.*)", listener.EventSourceHandler, dict(event_class = listener.JSONIdEvent, keepalive = args.keepalive)),
144 ], debug = args.debug, ctrl_host = args.control_host, ctrl_port = args.control_port)
145 application.listen(address = args.host, port = args.port)
146 trap_setup(args.host, args.port, application.settings['ctrl_host'], application.settings['ctrl_port'], args.token)
147 tornado.ioloop.IOLoop.instance().start()