blob: ecda15bfda7783dfbd08504313860cd132635d72 [file] [log] [blame]
"""
Copyright 2020 The Magma Authors.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
import _thread
import socket
from wsgiref.simple_server import (
ServerHandler,
WSGIRequestHandler,
WSGIServer,
make_server,
)
from common.misc_utils import get_ip_from_if
from configuration.service_configs import load_service_config
from logger import EnodebdLogger as logger
from state_machines.enb_acs_manager import StateMachineManager
from spyne.server.wsgi import WsgiApplication
from .models import CWMP_NS
from .rpc_methods import AutoConfigServer
from .spyne_mods import Tr069Application, Tr069Soap11
# Socket timeout in seconds. Should be set larger than the longest TR-069
# response time (typically for a GetParameterValues of the entire data model),
# measured at 168secs. Should also be set smaller than ENB_CONNECTION_TIMEOUT,
# to avoid incorrectly detecting eNodeB timeout.
SOCKET_TIMEOUT = 240
class tr069_WSGIRequestHandler(WSGIRequestHandler):
timeout = 10
# pylint: disable=attribute-defined-outside-init
def handle_single(self):
"""Handle a single HTTP request"""
self.raw_requestline = self.rfile.readline(65537)
if len(self.raw_requestline) > 65536:
self.requestline = ''
self.request_version = ''
self.command = ''
self.close_connection = 1
self.send_error(414)
return
if not self.parse_request(): # An error code has been sent, just exit
return
handler = ServerHandler(
self.rfile, self.wfile, self.get_stderr(), self.get_environ(),
)
handler.http_version = "1.1"
handler.request_handler = self # backpointer for logging
# eNodeB will sometimes close connection to enodebd.
# The cause of this is unknown, but we can safely ignore the
# closed connection, and continue as normal otherwise.
#
# While this throws a BrokenPipe exception in wsgi server,
# it also causes an AttributeError to be raised because of a
# bug in the wsgi server.
# https://bugs.python.org/issue27682
try:
handler.run(self.server.get_app())
except BrokenPipeError:
self.log_error("eNodeB has unexpectedly closed the TCP connection.")
def handle(self):
self.protocol_version = "HTTP/1.1"
self.close_connection = 0
try:
while not self.close_connection:
self.handle_single()
except socket.timeout as e:
self.log_error("tr069 WSGI Server Socket Timeout: %r", e)
self.close_connection = 1
return
except socket.error as e:
self.log_error("tr069 WSGI Server Socket Error: %r", e)
self.close_connection = 1
return
# Disable pylint warning because we are using same parameter name as built-in
# pylint: disable=redefined-builtin
def log_message(self, format, *args):
""" Overwrite message logging to use python logging framework rather
than stderr """
logger.debug("%s - %s", self.client_address[0], format % args)
# Disable pylint warning because we are using same parameter name as built-in
# pylint: disable=redefined-builtin
def log_error(self, format, *args):
""" Overwrite message logging to use python logging framework rather
than stderr """
logger.warning("%s - %s", self.client_address[0], format % args)
def tr069_server(state_machine_manager: StateMachineManager) -> None:
"""
TR-069 server
Inputs:
- acs_to_cpe_queue = instance of Queue
containing messages from parent process/thread to be sent to CPE
- cpe_to_acs_queue = instance of Queue
containing messages from CPE to be sent to parent process/thread
"""
config = load_service_config("enodebd")
AutoConfigServer.set_state_machine_manager(state_machine_manager)
app = Tr069Application(
[AutoConfigServer], CWMP_NS,
in_protocol=Tr069Soap11(validator='soft'),
out_protocol=Tr069Soap11(),
)
wsgi_app = WsgiApplication(app)
try:
ip_address = get_ip_from_if(config['tr069']['interface'])
except (ValueError, KeyError) as e:
# Interrupt main thread since process should not continue without TR-069
_thread.interrupt_main()
raise e
socket.setdefaulttimeout(SOCKET_TIMEOUT)
logger.info(
'Starting TR-069 server on %s:%s',
ip_address, config['tr069']['port'],
)
server = make_server(
ip_address,
config['tr069']['port'], wsgi_app,
WSGIServer, tr069_WSGIRequestHandler,
)
# Note: use single-thread server, to avoid state contention
try:
server.serve_forever()
finally:
# Log error and interrupt main thread, to ensure that entire process
# is restarted if this thread exits
logger.error('Hit error in TR-069 thread. Interrupting main thread.')
_thread.interrupt_main()