diff --git a/README.md b/README.md index 7361f0f..6a2f6a3 100644 --- a/README.md +++ b/README.md @@ -1 +1,59 @@ -# lib-protocol-proxy \ No newline at end of file +# Protocol Proxy +![Python 3.10](https://img.shields.io/badge/python-3.10-blue.svg) +![Python 3.11](https://img.shields.io/badge/python-3.11-blue.svg) +[![Passing?](https://github.com/eclipse-volttron/lib-protocol-proxy/actions/workflows/run-tests.yml/badge.svg)](https://github.com/eclipse-volttron/lib-protocol-proxy/actions/workflows/run-tests.yml) +[![pypi version](https://img.shields.io/pypi/v/protocol-proxy.svg)](https://pypi.org/project/protocol-proxy/) + +This library provides the user with the ability to automatically deploy and manager proxy processes for handling +network communication with remote devices using various protocols. A proxy to each remote peer is established in +a separate process from the managing application. A manager class handles socket communication between the proxy +subprocess and its owner. Individual protocols are implemented as plugins to this library. Integration with +event and asyncio event loops are supported for each of the proxy and manager processes. + + +## Automatically installed dependencies +- python = ">=3.10,<4.0" + +[//]: # (# Documentation) + +[//]: # (More detailed documentation can be found on [ReadTheDocs](https://eclipse-volttron.readthedocs.io/en/latest/external-docs/lib-protocol-proxy/index.html. The RST source) + +[//]: # (of the documentation for this component is located in the "docs" directory of this repository.) + +# Installation +This library can be installed using pip: + +```shell +pip install lib-protocol-proxy +``` + +Protocol Proxy plugins should include "protocol-proxy" as a requirement, so users of existing +plugins are encouraged to instead install the plugin for that pacakge directly. + +# Development +This library is maintained by the VOLTTRON Development Team. + +Please see the following [guidelines](https://github.com/eclipse-volttron/volttron-core/blob/develop/CONTRIBUTING.md) +for contributing to this and/or other VOLTTRON repositories. + +[//]: # (Please see the following helpful guide about [using the Protocol Proxy](https://github.com/eclipse-volttron/lib-protocol-proxy/blob/develop/developing_with_protocol_proxy.md)) + +[//]: # (in your VOLTTRON agent or other applications.) + +# Disclaimer Notice + +This material was prepared as an account of work sponsored by an agency of the +United States Government. Neither the United States Government nor the United +States Department of Energy, nor Battelle, nor any of their employees, nor any +jurisdiction or organization that has cooperated in the development of these +materials, makes any warranty, express or implied, or assumes any legal +liability or responsibility for the accuracy, completeness, or usefulness or any +information, apparatus, product, software, or process disclosed, or represents +that its use would not infringe privately owned rights. + +Reference herein to any specific commercial product, process, or service by +trade name, trademark, manufacturer, or otherwise does not necessarily +constitute or imply its endorsement, recommendation, or favoring by the United +States Government or any agency thereof, or Battelle Memorial Institute. The +views and opinions of authors expressed herein do not necessarily state or +reflect those of the United States Government or any agency thereof. diff --git a/pyproject.toml b/pyproject.toml index c258a95..099a91c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "poetry.core.masonry.api" profile = "black" [tool.mypy] -python_version = 3.10 +python_version = "3.10" show_error_context = true pretty = true show_column_numbers = true @@ -29,7 +29,7 @@ ignore_missing_imports = true [tool.poetry] name = "protocol-proxy" -version = "2.0.0rc1" +version = "2.0.0rc2" description = "A system for launching and communicating with a proxy application for network communication which runs in a separate process.." authors = ["The VOLTTRON Development Team "] license = "Apache License 2.0" diff --git a/src/protocol_proxy/ipc/asyncio.py b/src/protocol_proxy/ipc/asyncio.py index 8945de7..8878cd4 100644 --- a/src/protocol_proxy/ipc/asyncio.py +++ b/src/protocol_proxy/ipc/asyncio.py @@ -91,7 +91,10 @@ async def _setup_inbound_server(self, socket_params: SocketParams = None): f' on any port in range: {self.min_port} - {self.max_port}.') break else: - self.inbound_params = SocketParams(*self.inbound_server.sockets[0].getsockname()) + # Only take first 2 elements (host, port) from getsockname() + # IPv6 sockets return 4-tuple (host, port, flowinfo, scope_id) + sockname = self.inbound_server.sockets[0].getsockname() + self.inbound_params = SocketParams(sockname[0], sockname[1]) break async def start(self, *_, **__): diff --git a/src/protocol_proxy/ipc/gevent.py b/src/protocol_proxy/ipc/gevent.py index 4d8a0c1..293e987 100644 --- a/src/protocol_proxy/ipc/gevent.py +++ b/src/protocol_proxy/ipc/gevent.py @@ -146,12 +146,19 @@ def _receive_headers(self, s: socket) -> ProtocolHeaders | None: try: received = s.recv(2) if len(received) == 0: - _log.warning(f'{self.proxy_name} received closed socket from ({s.getpeername()}.') + try: + peer_name = f' from {s.getpeername()}.' + except OSError: + peer_name = '.' + _log.warning(f'{self.proxy_name} received closed socket {peer_name}') return None version_num = struct.unpack('>H', received)[0] if not (protocol := self.PROTOCOL_VERSION.get(version_num)): - raise NotImplementedError(f'Unknown protocol version ({version_num})' - f' received from: {s.getpeername()}') + try: + peer_name = f' received from: {s.getpeername()}.' + except OSError: + peer_name = '.' + raise NotImplementedError(f'Unknown protocol version ({version_num}){peer_name}') header_bytes = s.recv(protocol.HEADER_LENGTH) if len(header_bytes) == protocol.HEADER_LENGTH: return protocol.unpack(header_bytes) @@ -194,11 +201,19 @@ def _receive_socket(self, s: socket): s.close() done = True elif headers: + try: + peer_name = f' from {s.getpeername()}' + except OSError: + peer_name = '' _log.warning(f'{self.proxy_name}: Received unknown method name: {headers.method_name}' - f' from {s.getpeername()} with request ID: {headers.request_id}') + f' {peer_name} with request ID: {headers.request_id}') s.close() else: - _log.warning(f'{self.proxy_name}: Unable to read headers from socket: {s.getpeername()}') + try: + peer_name = f': {s.getpeername()}.' + except OSError: + peer_name = '.' + _log.warning(f'{self.proxy_name}: Unable to read headers from socket: {peer_name}') s.close() def _send_headers(self, s: socket, data_length: int, request_id: int, response_expected: bool, method_name: str, @@ -216,7 +231,11 @@ def _send_headers(self, s: socket, data_length: int, request_id: int, response_e def _send_socket(self, s: socket): _log.debug(f'{self.proxy_name}: IN SEND SOCKET') if not (message := self.outbound_messages.get(s)): - _log.warning(f'Outbound socket to {s.getpeername()} was ready, but no outbound message was found.') + try: + peer_name = f'to {s.getpeername()}' + except OSError: + peer_name = '' + _log.warning(f'Outbound socket to {peer_name} was ready, but no outbound message was found.') elif isinstance(message.payload, AsyncResult) and not message.payload.ready(): self.outbounds.add(s) _log.debug('IN SEND SOCKET, WAS ADDED BACK TO OUTBOUND BECAUSE ASYNC_RESULT WAS NOT READY.') diff --git a/src/protocol_proxy/proxy/asyncio.py b/src/protocol_proxy/proxy/asyncio.py index ea15b27..f1ca147 100644 --- a/src/protocol_proxy/proxy/asyncio.py +++ b/src/protocol_proxy/proxy/asyncio.py @@ -22,7 +22,10 @@ def __init__(self, manager_address: str, manager_port: int, manager_id: UUID, ma token=manager_token) def get_local_socket_params(self) -> SocketParams: - return self.inbound_server.sockets[0].getsockname() + # Only take first 2 elements (host, port) from getsockname() + # IPv6 sockets return 4-tuple (host, port, flowinfo, scope_id) + sockname = self.inbound_server.sockets[0].getsockname() + return SocketParams(sockname[0], sockname[1]) async def send_registration(self, remote: AsyncioProtocolProxyPeer): _log.debug(f"[send_registration] Attempting to register with manager at: {remote} (type={type(remote)})")