blob: ecda15bfda7783dfbd08504313860cd132635d72 [file] [log] [blame]
Wei-Yu Chen49950b92021-11-08 19:19:18 +08001"""
2Copyright 2020 The Magma Authors.
3
4This source code is licensed under the BSD-style license found in the
5LICENSE file in the root directory of this source tree.
6
7Unless required by applicable law or agreed to in writing, software
8distributed under the License is distributed on an "AS IS" BASIS,
9WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10See the License for the specific language governing permissions and
11limitations under the License.
12"""
13
14import _thread
15import socket
16from wsgiref.simple_server import (
17 ServerHandler,
18 WSGIRequestHandler,
19 WSGIServer,
20 make_server,
21)
22
23from common.misc_utils import get_ip_from_if
24from configuration.service_configs import load_service_config
25from logger import EnodebdLogger as logger
26from state_machines.enb_acs_manager import StateMachineManager
27from spyne.server.wsgi import WsgiApplication
28
29from .models import CWMP_NS
30from .rpc_methods import AutoConfigServer
31from .spyne_mods import Tr069Application, Tr069Soap11
32
33# Socket timeout in seconds. Should be set larger than the longest TR-069
34# response time (typically for a GetParameterValues of the entire data model),
35# measured at 168secs. Should also be set smaller than ENB_CONNECTION_TIMEOUT,
36# to avoid incorrectly detecting eNodeB timeout.
37SOCKET_TIMEOUT = 240
38
39
40class tr069_WSGIRequestHandler(WSGIRequestHandler):
41 timeout = 10
42 # pylint: disable=attribute-defined-outside-init
43
44 def handle_single(self):
45 """Handle a single HTTP request"""
46 self.raw_requestline = self.rfile.readline(65537)
47 if len(self.raw_requestline) > 65536:
48 self.requestline = ''
49 self.request_version = ''
50 self.command = ''
51 self.close_connection = 1
52 self.send_error(414)
53 return
54
55 if not self.parse_request(): # An error code has been sent, just exit
56 return
57
58 handler = ServerHandler(
59 self.rfile, self.wfile, self.get_stderr(), self.get_environ(),
60 )
61 handler.http_version = "1.1"
62 handler.request_handler = self # backpointer for logging
63
64 # eNodeB will sometimes close connection to enodebd.
65 # The cause of this is unknown, but we can safely ignore the
66 # closed connection, and continue as normal otherwise.
67 #
68 # While this throws a BrokenPipe exception in wsgi server,
69 # it also causes an AttributeError to be raised because of a
70 # bug in the wsgi server.
71 # https://bugs.python.org/issue27682
72 try:
73 handler.run(self.server.get_app())
74 except BrokenPipeError:
75 self.log_error("eNodeB has unexpectedly closed the TCP connection.")
76
77 def handle(self):
78 self.protocol_version = "HTTP/1.1"
79 self.close_connection = 0
80
81 try:
82 while not self.close_connection:
83 self.handle_single()
84
85 except socket.timeout as e:
86 self.log_error("tr069 WSGI Server Socket Timeout: %r", e)
87 self.close_connection = 1
88 return
89
90 except socket.error as e:
91 self.log_error("tr069 WSGI Server Socket Error: %r", e)
92 self.close_connection = 1
93 return
94
95 # Disable pylint warning because we are using same parameter name as built-in
96 # pylint: disable=redefined-builtin
97 def log_message(self, format, *args):
98 """ Overwrite message logging to use python logging framework rather
99 than stderr """
100 logger.debug("%s - %s", self.client_address[0], format % args)
101
102 # Disable pylint warning because we are using same parameter name as built-in
103 # pylint: disable=redefined-builtin
104 def log_error(self, format, *args):
105 """ Overwrite message logging to use python logging framework rather
106 than stderr """
107 logger.warning("%s - %s", self.client_address[0], format % args)
108
109
110def tr069_server(state_machine_manager: StateMachineManager) -> None:
111 """
112 TR-069 server
113 Inputs:
114 - acs_to_cpe_queue = instance of Queue
115 containing messages from parent process/thread to be sent to CPE
116 - cpe_to_acs_queue = instance of Queue
117 containing messages from CPE to be sent to parent process/thread
118 """
119 config = load_service_config("enodebd")
120
121 AutoConfigServer.set_state_machine_manager(state_machine_manager)
122
123 app = Tr069Application(
124 [AutoConfigServer], CWMP_NS,
125 in_protocol=Tr069Soap11(validator='soft'),
126 out_protocol=Tr069Soap11(),
127 )
128 wsgi_app = WsgiApplication(app)
129
130 try:
131 ip_address = get_ip_from_if(config['tr069']['interface'])
132 except (ValueError, KeyError) as e:
133 # Interrupt main thread since process should not continue without TR-069
134 _thread.interrupt_main()
135 raise e
136
137 socket.setdefaulttimeout(SOCKET_TIMEOUT)
138 logger.info(
139 'Starting TR-069 server on %s:%s',
140 ip_address, config['tr069']['port'],
141 )
142 server = make_server(
143 ip_address,
144 config['tr069']['port'], wsgi_app,
145 WSGIServer, tr069_WSGIRequestHandler,
146 )
147
148 # Note: use single-thread server, to avoid state contention
149 try:
150 server.serve_forever()
151 finally:
152 # Log error and interrupt main thread, to ensure that entire process
153 # is restarted if this thread exits
154 logger.error('Hit error in TR-069 thread. Interrupting main thread.')
155 _thread.interrupt_main()