Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow alternative agentx socket configuration #92

Merged
merged 12 commits into from
Nov 21, 2018
1 change: 1 addition & 0 deletions src/ax_interface/constants.py
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
111 changes: 107 additions & 4 deletions src/ax_interface/socket_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"""
import asyncio
import logging
import re

from . import logger, constants
from .protocol import AgentX
Expand All @@ -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.
Expand All @@ -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
Expand Down