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