Skip to content

Commit

Permalink
50 bug ezsnmperror usm unknown security name no such user exists (#108)
Browse files Browse the repository at this point in the history
* Update globals.py

- add a context name. This helps remove the USM errors.

* Revert "Update globals.py"

This reverts commit af67198.

* Update globals.py

- current workaound to usm error is too just try again.

* Work Around To USM Bug

-TLDR: Just try again if any failures
- updated integrations tests for proper testing with no sleeps.
- updated GA for tests to run 3 times. Parsing different things each run. This is the ground work for a PR comment.

* Fix :)

- wip for bug fix. only clear the cache when update session is called.

* Revert "Fix :)"

This reverts commit efbb9b9.

* Fix

- fixing the no such user exists error. We add an internal map keeping track of which users are being used. This will need to be performance tested.

* Mutex, Naming, etc

- added a mutex to protect the updated of the global map when under threads or procs.
- some session.py updates to get tests to pass.

* allow threas

allowing threads to remove v3 cache

* Update session.py

- adding __enter__ and __exit__ calls to use with calls. This will clean sessions on exit to prevent leaks.

* Black and Del

- ran black
- del old session in ez.py

* tests

- tests pass now

* Update build_and_publish_to_test_pypi.yml

go back to macos-latest

* GA

- making a seperate yaml file for integration tests.

* Update integration_tests.yml

- adding needs: check-source-changes

* Update integration_tests.yml

- forgot to add integration_tests to the path for test results.

* Update integration_tests.yml

- another shot at the upload path.

* Update integration_tests.yml

- okay maybe the path is correct now.

* Update integration_tests.yml

- adding a limit to run time. 10 minutes should be enough to run all the integration tests
- debug info to fix upload not finding the output files.

* Update integration_tests.yml

- okay, let's try wild card to find those darn files.

* Update integration_tests.yml

- another shot at wildcards for the upload path.

* Update integration_tests.yml

- okay, now it should be valid... I think.

* Oops

- ahh oops forgot to CD... silly me
- added a max try to the integration tests.

* Update integration_tests.yml

- one last time ? Maybe ?

* Update integration_tests.yml

- bump timeout to 15 minutes
- split execution into multiple steps.
- remove redirect of stdout and stderr.

* Update integration_tests.yml

- okay they run again, but uploading isn't working still. trying another  method.

* Update integration_tests.yml

- macos path fix

* - this fixes macos builds. ATM can only build x64_86 onces. Will need to look into it more.
  • Loading branch information
carlkidcrypto authored Jun 14, 2024
1 parent bd393cb commit cd17f82
Show file tree
Hide file tree
Showing 9 changed files with 165 additions and 53 deletions.
28 changes: 21 additions & 7 deletions ezsnmp/ez.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ def snmp_get(oids, **session_kargs):
"""

session = Session(**session_kargs)
return session.get(oids)
retval = session.get(oids)
del session
return retval


def snmp_set(oid, value, type=None, **session_kargs):
Expand All @@ -40,7 +42,9 @@ def snmp_set(oid, value, type=None, **session_kargs):
"""

session = Session(**session_kargs)
return session.set(oid, value, type)
retval = session.set(oid, value, type)
del session
return retval


def snmp_set_multiple(oid_values, **session_kargs):
Expand All @@ -56,7 +60,9 @@ def snmp_set_multiple(oid_values, **session_kargs):
"""

session = Session(**session_kargs)
return session.set_multiple(oid_values)
retval = session.set_multiple(oid_values)
del session
return retval


def snmp_get_next(oids, **session_kargs):
Expand All @@ -75,7 +81,9 @@ def snmp_get_next(oids, **session_kargs):
"""

session = Session(**session_kargs)
return session.get_next(oids)
retval = session.get_next(oids)
del session
return retval


def snmp_get_bulk(oids, non_repeaters=0, max_repetitions=10, **session_kargs):
Expand All @@ -99,7 +107,9 @@ def snmp_get_bulk(oids, non_repeaters=0, max_repetitions=10, **session_kargs):
"""

session = Session(**session_kargs)
return session.get_bulk(oids, non_repeaters, max_repetitions)
retval = session.get_bulk(oids, non_repeaters, max_repetitions)
del session
return retval


def snmp_walk(oids=".1.3.6.1.2.1", **session_kargs):
Expand All @@ -118,7 +128,9 @@ def snmp_walk(oids=".1.3.6.1.2.1", **session_kargs):
"""

session = Session(**session_kargs)
return session.walk(oids)
retval = session.walk(oids)
del session
return retval


def snmp_bulkwalk(
Expand All @@ -143,4 +155,6 @@ def snmp_bulkwalk(
"""

session = Session(**session_kargs)
return session.bulkwalk(oids, non_repeaters, max_repetitions)
retval = session.bulkwalk(oids, non_repeaters, max_repetitions)
del session
return retval
107 changes: 81 additions & 26 deletions ezsnmp/interface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,17 @@
#include <string.h>
#include <stdarg.h>
#include <memory>
#include <string>
#include <map>
#include <mutex>

#ifdef HAVE_REGEX_H
#include <regex.h>
#endif

std::map<std::string, uint64_t> g_in_use_v3_sessions;
std::mutex g_in_use_v3_sessions_mutex;

/* include bitarray data structure for v1 queries */
#include "simple_bitarray.h"
#include "interface.h"
Expand Down Expand Up @@ -511,7 +517,7 @@ int __snprint_value(char *buf, size_t buf_len,

case ASN_OBJECT_ID:
__snprintf_num_objid(buf, (oid *)(var->val.objid),
var->val_len / sizeof(oid));
var->val_len / sizeof(oid));
len = STRLEN(buf);
break;

Expand Down Expand Up @@ -1374,22 +1380,55 @@ int __send_sync_pdu(netsnmp_session *ss, netsnmp_pdu **pdu,
* Clears v3 user credentials from the local cache
*/

void __remove_user_from_cache(struct session_list *ss)
void __remove_v3_user_from_cache(struct session_list *ss)
{
struct usmUser *actUser = usm_get_userList();
while (actUser != NULL)
{
struct usmUser *dummy = actUser;
if (actUser->secName != NULL && ss->session->securityName != NULL &&
actUser->engineID != NULL && ss->session->contextEngineID != NULL &&
strcmp((const char *)dummy->secName, (const char *)ss->session->securityName) == 0 &&
strcmp((const char *)dummy->engineID, (const char *)ss->session->contextEngineID) == 0)
{
usm_remove_user(actUser);
actUser->next = NULL;
actUser->prev = NULL;
usm_free_user(actUser);
break;

auto security_name_str = std::string("");
auto act_user_sec_name_str = std::string("");
if (ss->session->securityName != NULL && actUser->secName != NULL)
{
security_name_str = std::string(ss->session->securityName);
act_user_sec_name_str = std::string(actUser->secName);
}

auto context_engine_id_str = std::string("");
auto act_user_engine_id_str = std::string("");
if (ss->session->contextEngineID != NULL && actUser->engineID != NULL)
{
context_engine_id_str = std::string(reinterpret_cast<char *>(ss->session->contextEngineID)); // u_char to char
act_user_engine_id_str = std::string(reinterpret_cast<char *>(actUser->engineID)); // u_char to char
}

if (!act_user_sec_name_str.empty() && !act_user_engine_id_str.empty() &&
security_name_str == act_user_sec_name_str &&
context_engine_id_str == act_user_engine_id_str)
{
g_in_use_v3_sessions_mutex.lock();
auto iter = g_in_use_v3_sessions.find(security_name_str);
if (iter != g_in_use_v3_sessions.end())
{
// Found and are we the only one?
if (g_in_use_v3_sessions[security_name_str] > 1)
{
g_in_use_v3_sessions[security_name_str] -= 1;
}
else if (g_in_use_v3_sessions[security_name_str] == 1)
{
usm_remove_user(actUser);
actUser->next = NULL;
actUser->prev = NULL;
usm_free_user(actUser);
g_in_use_v3_sessions[security_name_str] = 0;
}

g_in_use_v3_sessions_mutex.unlock();
break;
}
g_in_use_v3_sessions_mutex.unlock();
}
actUser = dummy->next;
}
Expand Down Expand Up @@ -1503,7 +1542,7 @@ void __py_netsnmp_update_session_errors(PyObject *session,

PyErr_Fetch(&type, &value, &traceback);

py_netsnmp_attr_set_string(session, (char *) "error_string", err_str,
py_netsnmp_attr_set_string(session, (char *)"error_string", err_str,
STRLEN(err_str));

tmp_for_conversion = PyLong_FromLong(err_num);
Expand Down Expand Up @@ -1605,21 +1644,17 @@ void *get_session_handle_from_capsule(PyObject *session_capsule)
/* Automatically called when Python reclaims session_capsule object. */
void delete_session_capsule(PyObject *session_capsule)
{
// Acquire the GIL
PyGILState_STATE gil_state = PyGILState_Ensure();

/* PyCapsule_GetPointer will raise an exception if it fails. */
struct session_capsule_ctx *ctx = static_cast<struct session_capsule_ctx *>(PyCapsule_GetPointer(session_capsule, NULL));
if (ctx)
{
// clear_user_list(); // Too dangerous, may disrupt other valid sessions
__remove_user_from_cache((struct session_list *)ctx->handle);
Py_BEGIN_ALLOW_THREADS;
__remove_v3_user_from_cache((struct session_list *)ctx->handle);
Py_END_ALLOW_THREADS;
snmp_sess_close(ctx->handle);
free(ctx);
}

// Release the GIL
PyGILState_Release(gil_state);
}

PyObject *netsnmp_create_session(PyObject *self, PyObject *args)
Expand Down Expand Up @@ -1697,6 +1732,9 @@ PyObject *netsnmp_create_session_v3(PyObject *self, PyObject *args)
int eng_boots;
int eng_time;
SnmpSession session = {0};
auto security_name_str = std::string("");
auto context_engine_id_str = std::string("");
std::map<std::string, uint64_t>::iterator iter;

if (!PyArg_ParseTuple(args, "isiiisisssssssii", &version,
&peer, &lport, &retries, &timeout,
Expand Down Expand Up @@ -1787,6 +1825,25 @@ PyObject *netsnmp_create_session_v3(PyObject *self, PyObject *args)
goto done;
}
}

// Before we return the session capsule add the SecurityName and ContextEngineID to the global map
security_name_str = std::string(session.securityName);
context_engine_id_str = std::string(reinterpret_cast<char *>(session.contextEngineID)); // u_char to char
g_in_use_v3_sessions_mutex.lock();
iter = g_in_use_v3_sessions.find(security_name_str);

if (iter == g_in_use_v3_sessions.end())
{
// Not Found
g_in_use_v3_sessions[security_name_str] = 1;
}
else
{
// Found
g_in_use_v3_sessions[security_name_str] += 1;
}
g_in_use_v3_sessions_mutex.unlock();

return create_session_capsule(&session);

done:
Expand All @@ -1813,6 +1870,7 @@ PyObject *netsnmp_create_session_tunneled(PyObject *self,
char *their_hostname;
char *trust_cert;
SnmpSession session = {0};
PyObject *return_value = NULL;

if (!PyArg_ParseTuple(args, (char *)"isiiisissssss", &version,
&peer, &lport, &retries, &timeout,
Expand All @@ -1821,15 +1879,15 @@ PyObject *netsnmp_create_session_tunneled(PyObject *self,
&our_identity, &their_identity,
&their_hostname, &trust_cert))
{
goto done;
return return_value;
}

if (version != 3)
{
PyErr_SetString(PyExc_ValueError,
(char *)"you must use SNMP version 3 as it's the only "
"version that supports tunneling");
goto done;
return return_value;
}

snmp_sess_init(&session);
Expand All @@ -1854,7 +1912,7 @@ PyObject *netsnmp_create_session_tunneled(PyObject *self,
{
py_log_msg(ERROR, (char *)"failed to initialize the transport "
"configuration container");
goto done;
return return_value;
}

session.transport_configuration->compare =
Expand All @@ -1881,9 +1939,6 @@ PyObject *netsnmp_create_session_tunneled(PyObject *self,
netsnmp_transport_create_config((char *)"trust_cert",
trust_cert));
return create_session_capsule(&session);

done:
return NULL;
}

PyObject *netsnmp_get(PyObject *self, PyObject *args)
Expand Down
2 changes: 1 addition & 1 deletion ezsnmp/interface.h
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ int __send_sync_pdu(netsnmp_session *ss, netsnmp_pdu **pdu,
netsnmp_pdu **response, int retry_nosuch,
char *err_str, int *err_num, int *err_ind,
bitarray *invalid_oids);
void __remove_user_from_cache(struct session_list *ss);
void __remove_v3_user_from_cache(struct session_list *ss);
PyObject *py_netsnmp_construct_varbind(void);
int py_netsnmp_attr_string(PyObject *obj, char *attr_name, char **val,
Py_ssize_t *len, PyObject **attr_bytes);
Expand Down
22 changes: 17 additions & 5 deletions ezsnmp/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,12 @@ def __init__(
# Create interface instance
self.update_session()

def __enter__(self):
return self

def __exit__(self, exc_type, exc_value, exc_traceback):
del self.sess_ptr

@property
def connect_hostname(self):
def format_hostname(hostname):
Expand Down Expand Up @@ -517,11 +523,17 @@ def update_session(self, **kwargs):
s.version = 1
s.update_session()
"""
for keyword, value in kwargs.items():
if keyword in self.__dict__:
self.__setattr__(keyword, value)
else:
warn('Keyword argument "{}" is not an attribute'.format(keyword))

if kwargs:
for keyword, value in kwargs.items():
if keyword in self.__dict__:
self.__setattr__(keyword, value)
else:
warn('Keyword argument "{}" is not an attribute'.format(keyword))

del self.sess_ptr
self.sess_ptr = None

# Tunneled
if self.tunneled:
# TODO: Determine the best way to test this
Expand Down
6 changes: 1 addition & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,5 @@ before-all = """brew update;
export PATH="/usr/local/opt/net-snmp/bin:$PATH";
echo 'export PATH="/usr/local/opt/net-snmp/sbin:$PATH"' >> ~/.zshrc;
export PATH="/usr/local/opt/net-snmp/sbin:$PATH";"""
# Reference - https://github.com/pypa/cibuildwheel/issues/1849
# Currently there seems to be issues with universal2 builds in GA.
# For now build arm64 and x86_64 for a workaround.
#archs = ["universal2"]
archs = ["arm64", "x86_64"]
archs = ["x86_64"]
repair-wheel-command = "delocate-wheel --require-archs {delocate_archs} -w {dest_dir} -v {wheel}"
21 changes: 12 additions & 9 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,15 +179,18 @@ def run(self):
.decode()
.strip()
)
prefix = (
check_output("net-snmp-config --prefix", shell=True).decode().strip()
)
_ = check_output(
"install_name_tool -change {0} {1} {2}/ezsnmp/interface{3}".format(
linked, lib_dir, b.build_platlib, ext
),
shell=True,
)

if linked:
# install_name_tool -change /opt/homebrew/opt/net-snmp/lib/libnetsnmp.40.dylib /opt/homebrew/Cellar/net-snmp/5.9.4/lib/libnetsnmp.dylib build/lib.macosx-10.9-universal2-cpython-39/ezsnmp/interface.cpython-39-darwin.so
cmd_to_run = "install_name_tool -change {0} {1} {2}/ezsnmp/interface{3}".format(
linked, lib_dir, b.build_platlib, ext
)

print("cmd_to_run: ", cmd_to_run)
_ = check_output(
cmd_to_run,
shell=True,
)


setup(
Expand Down
File renamed without changes.
File renamed without changes.
Loading

0 comments on commit cd17f82

Please sign in to comment.