Monitoring client connections on a cisco C9800-CL with 2.0.0p17 (CRE)

Hi everyone,

First of all, I’m French, so please forgive my possible mistakes in English :slightly_smiling_face:

I’m a beginner on checkMK and, after monitoring an old Cisco WLC with the 1.6 version, I try to do the same thing with a brand new controller (Cisco virtual C9800-CL) with the new 2.0 version…
And I don’t manage to get the number of clients connected to the three different SSID we have in the school.
I found how o get informations on the AP connected but I can’t go further…
Can anyone help me for this problem ?

Thanks.

Have a nice day,
Jean-Emmanuel

Hi @jehlp and welcome to the checkmk community.

The newer controllers are not recognized by checkmk because there ist another oid to check to determine the WLC device. We had this situation a few weeks ago and i created a pull request for the coding.
You can fix this by copying the file for the check (different directories between your versions) to the local path ~sitename/local/share/... and changing them. Take a look at the my pull request for details, if you need further help, let me know:

13611 FIX Add service detection for Cisco WLC C9800 · tribe29/checkmk@676234e (github.com)

There is even a difference between the C9800-L-C and C9800-C-L (appliance and virtual).

Please forgive my bad English, i am German :slight_smile:

1 Like

Hi @tosch and thank you for your reply.

I copy the file /opt/omd/versions/2.0.0p17.cre/lib/python3/cmk/base/plugins/agent_based/cisco_wlc.py to ~sitename/local/share/check_mk/agents/plugins and change it.
I add the two lines you mentioned in your pull request.
Nothing new.
I restart my site (omd stop, omd start).
Nothing new.
May I ask you if I made something wrong ?

Thanks a lot.

Have a nice day,
Jean-Emmanuel

Can you please check which oid is returned by querying the oid .1.3.6.1.2.1.1.2.0? This value is used to determine if the device is fitting the cisco_wlc check.
Don’t forget to reinventorize your host after the change.

This file needs to be copied to

/opt/omd/sites/sitename/local/lib/python3/cmk/base/plugins/agent_based/

There you can make then the modifications.

1 Like

Thanks @andreas-doehler, not yet familiar with the new file structure. :slight_smile:

Thanks to @andreas-doehler I copy the file on the right place : but nothing happens.

After that, I query the oid @tosch mentioned :

snmpwalk -v1 -c <community> <ip address> 1.3.6.1.2.1.1.2.0
iso.3.6.1.2.1.1.2.0 = OID: iso.3.6.1.4.1.9.1.2391

I replace .1.3.6.1.4.1.9.1.2860 by .1.3.6.1.4.1.9.1.2391 in the corrected plugin file, restart the site, full scan on the device : nothing new…
I noticed that there’s a new directory in ~sitename/local/lib/python3/cmk/base/plugins/agent_based : __pycache__

Did I do something wrong ?

Oh holy … i just realized, there is a whole extra section for 9800 devices at the cisco_wlc_clients check to handle them differently from other cisco WLCs. Seems like there is work too to get this working:

def parse_cisco_wlc_9800_clients(
    string_table: List[StringTable],
) -> WlcClientsSection[ClientsTotal]:
    section: WlcClientsSection[ClientsTotal] = WlcClientsSection()
    for (ssid_name,), (num_clients_str,) in zip(string_table[0], string_table[1]):
        num_clients = int(num_clients_str)
        section.total_clients += num_clients
        if ssid_name not in section.clients_per_ssid:
            section.clients_per_ssid[ssid_name] = ClientsTotal(0)
        section.clients_per_ssid[ssid_name].total += num_clients
    return section


register.snmp_section(
    name="cisco_wlc_9800_clients",
    parsed_section_name="wlc_clients",
    detect=matches(OID_sysObjectID, r"^\.1\.3\.6\.1\.4\.1\.9\.1\.2530"),
    parse_function=parse_cisco_wlc_9800_clients,
    fetch=[
        SNMPTree(
            base=".1.3.6.1.4.1.9.9.512.1.1.1.1",
            oids=[
                "4",  # CISCO-LWAPP-WLAN-MIB::cLWlanSsid
            ],
        ),
        SNMPTree(
            base=".1.3.6.1.4.1.14179.2.1.1.1",
            oids=[
                "38",  # AIRESPACE-WIRELESS-MIB::bsnDot11EssNumberOfMobileStations
            ],
        ),
    ],
)

The connected clients count isn’t important for us so i never have checked this before.

Ok, I’m ready to do the changes but I need to find the MIB to put the right base end oids in my modified plugins…
I will be back…

You can try to change this line and hope it will work already:

to
detect=matches(OID_sysObjectID, r"^\.1\.3\.6\.1\.4\.1\.9\.1\.(2530|2391)"),

The searched values should be below the oid tree .1.3.6.1.4.1.9.9.512.1.1.1.1 for the SSIDs and below the oid tree .1.3.6.1.4.1.14179.2.1.1.1 for connected clients.

I tried…
Unfortunately, it does not work !!
I’m now looking for a login to the cisco web site to be able to download the MIB file…
I’ll give you all the knowledge I can after that…

Hi,
I’m back with strange news. The MIB on the C9800 is the same as the others so I don’t understand why it’s not working !
When I do snmpwalk -Of -v1 -c <community> <ip> .1.3.6.1.4.1.9.9.512.1.1.1.1.4 I have the three SSID.

.iso.3.6.1.4.1.9.9.512.1.1.1.1.4.1 = STRING: "<SSID1>"
.iso.3.6.1.4.1.9.9.512.1.1.1.1.4.2 = STRING: "<SSID2>"
.iso.3.6.1.4.1.9.9.512.1.1.1.1.4.3 = STRING: "<SSID3>"

And when I do snmpwalk -Of -v1 -c <community> <ip> .1.3.6.1.4.1.14179.2.1.1.1.38, I have the number of connected clients.

.iso.3.6.1.4.1.14179.2.1.1.1.38.1 = Counter32: 87
.iso.3.6.1.4.1.14179.2.1.1.1.38.2 = Counter32: 3
.iso.3.6.1.4.1.14179.2.1.1.1.38.3 = Counter32: 21

My question is : what is wrong with my configuration ?

I hope somebody could help me !

Thanks.

Have a nice day,
Jean-Emmanuel

Hi @jehlp ,

as i wrote in the post #8 there is a dedicated section for 9800 systems. The sys object oid variate from system to system so i guess the dedicated section is not hit due to a different sys object oid.

Hi @tosch ,
I know and I made the modifications you suggest :

But I find I have a syntax error in the cisco_wlx.py file.
As I’m not a python coder I can see where the error is but I don’t know how to fix it…
A little help maybe ?
This the section with the error :

def parse_cisco_wlc_9800_clients(
    string_table: List[StringTable],
) -> WlcClientsSection[ClientsTotal]:
    section: WlcClientsSection[ClientsTotal] = WlcClientsSection()
    for (ssid_name,), (num_clients_str,) in zip(string_table[0], string_table[1]):
        num_clients = int(num_clients_str)
        section.total_clients += num_clients
        if ssid_name not in section.clients_per_ssid:
            section.clients_per_ssid[ssid_name] = ClientsTotal(0)
        section.clients_per_ssid[ssid_name].total += num_clients
    return section

The code checker raise an error on the section: WlcClientsSection[ClientsTotal] = WlcClientsSection() line.
Any idea ?

Thanks

Can you please provide the python stack trace and error message?

No problem :

File "cisco_wlc.py", line 86
    def parse_cisco_wlc(string_table: List[StringTable]) -> Section:
                                    ^
SyntaxError: invalid syntax

Here you are…

Thanks

This is not the point you mentioned before, this is located in the file you copied to the local path. Have you corrected the location of the file like @andreas-doehler mentioned?

The syntax error sounds like there is python2 running instead of python 3. I can’t see anything wrong with the syntax for python3.

1 Like

Hi @tosch ,
I’m sorry but you’re right…
I did not verify the version of python used by my check mk server… It was python2…
Now that I made the change, I still have a (null) warning rising when I put the plugin in the directory @andreas-doehler mentioned earlier…
This is what I have :

$ python cisco_wlc.py
Traceback (most recent call last):
  File "cisco_wlc.py", line 43, in <module>
    from .agent_based_api.v1 import (
ModuleNotFoundError: No module named '__main__.agent_based_api'; '__main__' is not a package

Is it better for you or do you need something else to help me, if you can ?
Thanks

You can’t call the files from checkmk just with python, it won’t get all includes, configs and co to run the code properly. After you fixed the things you can test the check by running cmk -vv [--debug] <host> and see how it goes.

1 Like

I try and get :
Error in agent based plugin cisco_wlc: name 'WlcClientsSection' is not defined
To be complete I give you the complete plugin that I put in /omd/sites/ensr/local/lib/python3/cmk/base/plugins/agent_based/ :

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright (C) 2019 tribe29 GmbH - License: GNU General Public License v2
# This file is part of Checkmk (https://checkmk.com). It is subject to the terms and
# conditions defined in the file COPYING, which is part of this source code package.
#------------------------------------------------------------------------------------
# Modifications for virtual C9800
# Should be put in :
# /omd/sites/ensr/local/lib/python3/cmk/base/plugins/agent_based
#------------------------------------------------------------------------------------
"""Cisco WLC sections and checks

>>> import re
>>> all(re.match(VERSION_CISCO_WLC_PATTERN, v) for v in (
...     ".1.3.6.1.4.1.14179.1.1.4.3",
...     ".1.3.6.1.4.1.9.1.1069",
...     ".1.3.6.1.4.1.9.1.1615",
...     ".1.3.6.1.4.1.9.1.1645",
...     ".1.3.6.1.4.1.9.1.1631",
...     ".1.3.6.1.4.1.9.1.1279",
...     ".1.3.6.1.4.1.9.1.1293",
...     ".1.3.6.1.4.1.9.1.2170",
...     ".1.3.6.1.4.1.9.1.2171",
...     ".1.3.6.1.4.1.9.1.2371",
...     ".1.3.6.1.4.1.9.1.2250",
...     ".1.3.6.1.4.1.9.1.2370",  # Cisco Aironet 2800
...     ".1.3.6.1.4.1.9.1.2391",  # Cisco WLC C9800 CL
...     ".1.3.6.1.4.1.9.1.2427",
...     ".1.3.6.1.4.1.9.1.2530",  # Cisco WLC 9800
... ))
True
>>> any(re.match(VERSION_CISCO_WLC_PATTERN, v) for v in (
...     ".1.3.6.1.4.1.14179",
...     ".1.3.6.1.4.1.9.1.1068",
...     ".1 3.6.1.4.1.9.1.1069",
...     "1.3.6.1.4.1.9.1.1069",
... ))
False
"""

from typing import Any, Dict, List, Mapping, Optional

from .agent_based_api.v1 import (
    SNMPTree,
    register,
    Service,
    Result,
    State as state,
    matches,
)
from .agent_based_api.v1.type_defs import (
    StringTable,
    CheckResult,
    DiscoveryResult,
)

Section = Dict[str, str]

OID_sysObjectID = ".1.3.6.1.2.1.1.2.0"
VERSION_CISCO_WLC_PATTERN = "|".join((
    ".1.3.6.1.4.1.14179.1.1.4.3",
    ".1.3.6.1.4.1.9.1.1069",
    ".1.3.6.1.4.1.9.1.1615",
    ".1.3.6.1.4.1.9.1.1645",
    ".1.3.6.1.4.1.9.1.1631",
    ".1.3.6.1.4.1.9.1.1279",
    ".1.3.6.1.4.1.9.1.1293",
    ".1.3.6.1.4.1.9.1.2170",
    ".1.3.6.1.4.1.9.1.2171",
    ".1.3.6.1.4.1.9.1.2371",
    ".1.3.6.1.4.1.9.1.2250",
    ".1.3.6.1.4.1.9.1.2370",  # Cisco Aironet 2800
    ".1.3.6.1.4.1.9.1.2391",  # Cisco WLC 9800 CL
    ".1.3.6.1.4.1.9.1.2427",
    ".1.3.6.1.4.1.9.1.2489",
    ".1.3.6.1.4.1.9.1.2530",  # Cisco WLC 9800
)).replace(".", r"\.")

map_states = {
    "1": (state.OK, "online"),
    "2": (state.CRIT, "critical"),
    "3": (state.WARN, "warning"),
}


def parse_cisco_wlc(string_table: List[StringTable]) -> Section:
    """
    >>> parse_cisco_wlc([[['AP19', '1'], ['AP02', '1']]])
    {'AP19': '1', 'AP02': '1'}
    """
    return dict(string_table[0])  # type: ignore[arg-type]


def discovery_cisco_wlc(section: Section) -> DiscoveryResult:
    """
    >>> list(discovery_cisco_wlc({'AP19': '1', 'AP02': '1'}))
    [Service(item='AP19'), Service(item='AP02')]
    """
    yield from (Service(item=item) for item in section)


def _node_not_found(item: str, params: Mapping[str, Any]) -> Result:
    infotext = "Accesspoint not found"
    for ap_name, ap_state in params.get("ap_name", []):
        if item.startswith(ap_name):
            return Result(state=ap_state, summary=infotext)
    return Result(state=state.CRIT, summary=infotext)


def _ap_info(node: Optional[str], wlc_status: str) -> Result:
    status, state_readable = map_states.get(wlc_status, (state.UNKNOWN, "unknown[%s]" % wlc_status))
    return Result(
        state=status,
        summary="Accesspoint: %s%s" % (state_readable,
                                       (' (connected to %s)' % node) if node else ""),
    )


def check_cisco_wlc(item: str, params: Mapping[str, Any], section: Section) -> CheckResult:
    """
    >>> list(check_cisco_wlc("AP19", {}, {'AP19': '1', 'AP02': '1'}))
    [Result(state=<State.OK: 0>, summary='Accesspoint: online')]
    >>> list(check_cisco_wlc("AP18", {}, {'AP19': '1', 'AP02': '1'}))
    [Result(state=<State.CRIT: 2>, summary='Accesspoint not found')]
    """
    if item in section:
        yield _ap_info(None, section[item])
    else:
        yield _node_not_found(item, params)


def cluster_check_cisco_wlc(
    item: str,
    params: Mapping[str, Any],
    section: Mapping[str, Section],
) -> CheckResult:
    """
    >>> list(cluster_check_cisco_wlc("AP19", {}, {"node1": {'AP19': '1', 'AP02': '1'}}))
    [Result(state=<State.OK: 0>, summary='Accesspoint: online (connected to node1)')]
    >>> list(cluster_check_cisco_wlc("AP18", {}, {"node1": {'AP19': '1', 'AP02': '1'}}))
    [Result(state=<State.CRIT: 2>, summary='Accesspoint not found')]
    """
    for node, node_section in section.items():
        if item in node_section:
            yield _ap_info(node, node_section[item])
            return
    yield _node_not_found(item, params)


register.snmp_section(
    name="cisco_wlc",
    detect=matches(OID_sysObjectID, VERSION_CISCO_WLC_PATTERN),
    parse_function=parse_cisco_wlc,
    fetch=[
        SNMPTree(base=".1.3.6.1.4.1.14179.2.2.1.1", oids=[
            "3",
            "6",
        ]),
    ],
)


register.check_plugin(
    name="cisco_wlc",  # name taken from pre-1.7 plugin
    service_name="AP %s",
    discovery_function=discovery_cisco_wlc,
    check_default_parameters={},
    check_ruleset_name="cisco_wlc",
    check_function=check_cisco_wlc,
    cluster_check_function=cluster_check_cisco_wlc,
)

#------------------------------------------------------------------------------------
# C9800 Specifications
#------------------------------------------------------------------------------------

def parse_cisco_wlc_9800_clients(
    string_table: List[StringTable],
) -> WlcClientsSection[ClientsTotal]:
    section: WlcClientsSection[ClientsTotal] = WlcClientsSection()
    for (ssid_name,), (num_clients_str,) in zip(string_table[0], string_table[1]):
        num_clients = int(num_clients_str)
        section.total_clients += num_clients
        if ssid_name not in section.clients_per_ssid:
            section.clients_per_ssid[ssid_name] = ClientsTotal(0)
        section.clients_per_ssid[ssid_name].total += num_clients
    return section


register.snmp_section(
    name="cisco_wlc_9800_clients",
    parsed_section_name="wlc_clients",
    detect=matches(OID_sysObjectID, r"^\.1\.3\.6\.1\.4\.1\.9\.1\.2530|2391"),
    parse_function=parse_cisco_wlc_9800_clients,
    fetch=[
        SNMPTree(
            base=".1.3.6.1.4.1.9.9.512.1.1.1.1",
            oids=[
                "4",  # CISCO-LWAPP-WLAN-MIB::cLWlanSsid
            ],
        ),
        SNMPTree(
            base=".1.3.6.1.4.1.14179.2.1.1.1",
            oids=[
                "38",  # AIRESPACE-WIRELESS-MIB::bsnDot11EssNumberOfMobileStations
            ],
        ),
    ],
)

Still got problems…