From 660a7528d960cfa05701b20a1bcf5a4636398db2 Mon Sep 17 00:00:00 2001 From: Michel Moriniaux Date: Tue, 20 Nov 2018 21:42:41 -0800 Subject: [PATCH] Allow alternative agentx socket configuration (#92) --- src/ax_interface/constants.py | 1 + src/ax_interface/socket_io.py | 111 ++++++++++++++++++++++++++++++++-- 2 files changed, 108 insertions(+), 4 deletions(-) diff --git a/src/ax_interface/constants.py b/src/ax_interface/constants.py index 5577fd17c..1c5b8a8ec 100644 --- a/src/ax_interface/constants.py +++ b/src/ax_interface/constants.py @@ -1,6 +1,7 @@ from enum import Enum, unique AGENTX_SOCKET_PATH = '/var/agentx/master' +SNMPD_CONFIG_FILE = '/etc/snmp/snmpd.conf' # https://tools.ietf.org/html/rfc2741#section-6.1 -- PDU Header definition # Headers are fixed at 20 bytes. diff --git a/src/ax_interface/socket_io.py b/src/ax_interface/socket_io.py index 4fe31f5b8..96ca9ab16 100644 --- a/src/ax_interface/socket_io.py +++ b/src/ax_interface/socket_io.py @@ -6,6 +6,7 @@ """ import asyncio import logging +import re from . import logger, constants from .protocol import AgentX @@ -25,6 +26,91 @@ def __init__(self, mib_table, run_event, loop): self.transport = self.ax_socket = None + self.ax_socket_path = constants.AGENTX_SOCKET_PATH + + # The following code reads the snmp config file to see if the Agentx Socket path has been redefined + # from the default RFC value '/var/agentx/master'. We do not implement reading the SNMP daemon's + # command line to check if it has been redefined there, this should be an effort for the future + # perhaps via a psutil().cmdline() call + + # open the snmpd config file and search for a agentx socket redefinition. Exceptions will be raised + # if the constants.SNMPD_CONFIG_FILE or the file in itself do not exist + pattern = re.compile("^agentxsocket\s+(.*)$", re.IGNORECASE) + try : + with open(constants.SNMPD_CONFIG_FILE,'rt') as config_file: + for line in config_file: + match = pattern.search(line) + if match: + self.ax_socket_path = match.group(1) + except: + logger.warning("SNMPD config file not found, using default agentx file socket") + + self.parse_socket() + logger.info("Using agentx socket type " + self.ax_socket_type + " with path " + self.ax_socket_path) + + def parse_socket(self): + # Determine wether the socket method is supported + # extract the type and connection data + + # lets get the unsuported methods out of the way first + unsuported_list = ['ssh', 'dtlsudp', 'ipx', 'aal5pvc', 'udp'] + for method in unsuported_list: + if self.ax_socket_path.startswith(method): + # This is not a supported method + self.unsuported_method() + return + # Now the stuff that we are interested in + # First case: we have a simple number then its a local UDP port + # udp has been added to the unsuported_list because asyncio throws a not implemented error with udp + # we leave the code here for when it will be implemented + if self.ax_socket_path.isdigit(): + self.unsuported_method() + return + # if we have an explicit udp socket + if self.ax_socket_path.startswith('udp'): + self.unsuported_method() + return + # if we have an explicit tcp socket + if self.ax_socket_path.startswith('tcp'): + self.ax_socket_type = 'tcp' + self.host, self.port = self.get_ip_port(self.ax_socket_path.split(':',1)[1]) + return + # if we have an explicit unix domain socket + if self.ax_socket_path.startswith('unix'): + self.ax_socket_type = 'unix' + self.ax_socket_path = self.ax_socket_path.split(':',1)[1] + return + # unix is not compulsory so you can also have a plain path + if '/' in self.ax_socket_path: + self.ax_socket_type = 'unix' + return + # if at this point we haven't matched anything yet its that we are most likely left with a host:port pair so UDP + if ':' in self.ax_socket_path: + self.unsuported_method() + return + # we should never get here but if we do it's that there is garbage so lets revert to the default of snmp + logger.warning("There's something weird with " + self.ax_socket_path + " , using default agentx file socket") + self.ax_socket_path = constants.AGENTX_SOCKET_PATH + self.ax_socket_type = 'unix' + return + + def get_ip_port(self,address): + # determine if we only have a port or a ip:port tuple, must work with IPv6 + address_list = address.split(':') + if len(address_list) == 1: + # we only have a port + return 'localhost', address_list[0] + else: + # if we get here then either: we've got garbage, an ip:port or ipv6:port or hostname:port + # an IP or IPv6 only is illegal + address_list = address.rsplit(':',1) + return address_list[0], address_list[1] + + def unsuported_method(self): + logger.warning("Socket type " + self.ax_socket_path + " not supported, using default agentx file socket") + self.ax_socket_path = constants.AGENTX_SOCKET_PATH + self.ax_socket_type = 'unix' + async def connection_loop(self): """ Try/Retry connection coroutine to attach the socket. @@ -37,10 +123,27 @@ async def connection_loop(self): try: logger.info("Attempting AgentX socket bind...".format()) - connection_routine = self.loop.create_unix_connection( - protocol_factory=lambda: AgentX(self.mib_table, self.loop), - path=constants.AGENTX_SOCKET_PATH, - sock=self.ax_socket) + # Open the connection to the Agentx socket, we check the socket string to + # lets open our socket according to its detected type + if self.ax_socket_type == 'unix': + connection_routine = self.loop.create_unix_connection( + protocol_factory=lambda: AgentX(self.mib_table, self.loop), + path=self.ax_socket_path, + sock=self.ax_socket) + elif self.ax_socket_type == 'udp': + # we should not land here as the udp method is in the unsuported list + # testing shows that async_io throws a NotImplementedError when udp is used + # the code remains for when asyncio will implement it + connection_routine = self.loop.create_datagram_endpoint( + protocol_factory=lambda: AgentX(self.mib_table, self.loop), + remote_addr=(self.host,self.port), + sock=self.ax_socket) + elif self.ax_socket_type == 'tcp': + connection_routine = self.loop.create_connection( + protocol_factory=lambda: AgentX(self.mib_table, self.loop), + host=self.host, + port=self.port, + sock=self.ax_socket) # Initiate the socket connection self.transport, protocol = await connection_routine