Introduction
The IP Phone Module is designed to discover and manage IP phones within multiple CUCM (Cisco Unified Communication Manager). Built as part of the Karama project, this module scans the network to identify registered IP phones and automatically registers them as device within the Sipmon NMS (Network Management System). This integration allows for centralized monitoring and management of IP phones through the Sipmon NMS platform.
Features:
Scheduled Scanning
Scanning occurs at 5-minute intervals daily, ensuring that network changes are captured with minimal delay.
Automatic Registration
Each scanned device active within the network is automatically registered with the NMS. For example, a new IP phone connecting to the network will be detected during the next scheduled scan and registered as a device in the NMS. During subsequent scans, the registered device will be updated if significant changes occur, such as deactivation or a change in its assigned IP address. Discovered devices will be registered under the IP Phone Custom template, which users can further customize to add more functionality that the devices can inherit.
IP Phone Interface
![[Pasted image 20250215100324.png | float-right | 300]] Direct interaction with the phone network allows real-time information retrieval, including switch details for connected devices. This can be managed using the module's built-in interface for user efficiency. > [!info] Highly experimental > > This feature is currently in an experimental stage and is planned for future release.
REST API
The system leverages microservice functionality through a REST API, exposing it to other modules for interaction. This provides greater flexibility within the system. Endpoints:
/{base_uri}api/{version}/phones/{id}: Get single phone details/{base_uri}api/{version}/phones: Get found/registered phone devices in NMS/{base_uri}api/{version}/phones/scan: Scan devices in the CUCMs
For a deep dive into the module API, see [[IP Phone module REST API | here]]
Structure:
Unlike other Sipmon modules, this module integrates directly into the Sipmon core, leveraging its core features for seamless operation. The structure can be broken down into two parts:
- Back-end: Fused with the Sipmon framework.
- Front-end: Integrated into the Sipmon web interface.
Back-end
This uses a Symfony-based file/folder convention:
├── composer.json
├── config
│ ├── packages
│ │ ├── dev
│ │ │ └── SipmonPhone.yaml
│ │ ├── prod
│ │ │ └── SipmonPhone.yaml
│ │ └── serializer
│ │ ├── phone.yml
│ │ └── SipmonPhone
│ │ └── phone.yml
│ ├── routes
│ │ └── SipmonPhone.yaml
│ ├── serializer
│ │ └── SipmonPhone
│ │ └── phone.yml
│ ├── sipmon-phone.config.php
│ └── sipmon.php
├── phpunit.xml
├── src
│ └── SipmonPhone
│ ├── Application
│ │ └── Controller
│ │ └── SipmonPhone.php
│ ├── Domain
│ │ └── Phone
│ │ ├── Entity
│ │ │ ├── H323Trunk.php
│ │ │ ├── Host.php
│ │ │ └── Phone.php
│ │ └── Repository
│ │ ├── HostExtendedRelationRepository.php
│ │ ├── HostGroupRelationRepository.php
│ │ ├── HostGroupRepository.php
│ │ ├── HostRepository.php
│ │ ├── HostTemplateRelationRepository.php
│ │ ├── NsHostRelationRepository.php
│ │ └── PhoneRepository.php
│ ├── Infrastructure
│ │ ├── AXLClient.php
│ │ ├── AXLSearchCriteria.php
│ │ ├── Client.php
│ │ ├── Enums
│ │ │ ├── DeviceModel.php
│ │ │ └── StatusReason.php
│ │ ├── ReturnTags.php
│ │ ├── SXLClient.php
│ │ ├── SXLSelectionCriteria.php
│ │ └── Utils
│ │ ├── ArrayConverter.php
│ │ ├── ConfigInterface.php
│ │ ├── Config.php
│ │ ├── DeviceModel.php
│ │ ├── DOMParser.php
│ │ ├── Session.php
│ │ ├── SoapClient.php
│ │ ├── SoapClientWrapper.php
│ │ └── StatusReason.php
│ ├── Schema
│ │ ├── AXLAPI.wsdl
│ │ ├── AXLEnums.xsd
│ │ ├── AXLSoap.xsd
│ │ ├── RisPort.wsdl
│ │ └── RISService70.wsdl
│ ├── Temp
│ │ └── Serviceability.htm
│ └── Tests
│ ├── assets
│ │ ├── AXLAPI.wsdl
│ │ ├── AXLEnums.xsd
│ │ ├── AXLSoap.xsd
│ │ ├── conf-1.php
│ │ ├── conf-2.php
│ │ ├── conf.php
│ │ ├── RisPort.wsdl
│ │ └── SoapClientDouble.php
│ ├── Domain
│ │ └── PhoneRepositoryTest.php
│ ├── Infrastructure
│ │ ├── AXLClientTest.php
│ │ └── SXLClientTest.php
│ └── SipmonPhoneTest.php
└── tests
└── php
├── bootstrap.php
├── Centreon
│ └── DependencyInjector.php
└── polyfill.phpFront-end
This part is similar to the module structure used in other Sipmon modules, except for the web services.
www
└── modules
└── phone
├── conf.php
├── images
│ ├── centreon.png
│ └── phone-interface.png
├── php
│ ├── clear_cache.php
│ ├── install.php
│ └── uninstall.php
├── react
│ ├── hooks
│ ├── package.json
│ └── pages
├── routes
│ ├── SipmonPhone.yaml
│ └── SipmonPhone.yaml.wait
├── sql
│ ├── index.html
│ ├── install.sql
│ └── uninstall.sql
└── staticWe will discuss these structure in the [[Module Structure | here]]
Critical Points/Challenges
As you may have noticed, we are using CUCM's AXL/SXL APIs under the hood to retrieve connected devices in the clusters. Having an API was a huge help to us, but we had to develop it under some constraints maintained by CUCM. Below, we have mentioned a few critical points that we have faced and solutions that we have come up with. We think that this will be helpful to others.
[!! warning:Case Study]
AXL API Restrictions
AXL acts as a provisioning and configuration API, not as a real-time API. So, at the moment, we are using the API to retrieve all the configured phones from the CUCM. For the time being, we are only hitting the reading endpoints, and we faced the following restrictions:
- AXL request default response limit.
- Read requests are also dynamically throttled based on the size.
For example:
List <object> and ExecuteSQLQuery methods that result in a data set greater than 8 MB will return Query request too large. Total rows matched: <Matched Rows>. Suggested row fetch: less than <Number of Rows>. Further reading
[!! success:Solution]
To get the complete phone list, we have used the ExecuteSQLQuery instead of ListPhones to resolve the response limitation.
Backward Compatibility
Please keep in mind that the ExecuteSQLQuery doesn't support backward compatibility like Listing objects
Also, this suggests we need to be aware of how many bytes we'll receive when hitting the endpoints, and we need to pre-calculate this beforehand. We had to minimize the number of columns that we need to query, so we only stick with the MAC ID so that we can calculate the size we receive.
For example:
Estimation guide that assumes a strict format of 12 hexadecimal characters for each MAC address (plus the SEP prefix), which typically totals 15 ASCII characters:
- Field size: 12 hex digits plus
SEP→ 15 ASCII characters = 15 bytes for the name. - XML overhead: ~200–300 bytes per returned phone record.
- Total per record: ~215–315 bytes.
- 8 MB total: With ~250 bytes per record as a middle estimate, 8 MB / 250 ≈ 32,000 records.
- Concurrent queries: Cisco recommends that, while one large query is running, any concurrent queries should be smaller than 2 MB total → ~2 MB / 250 ≈ 8,000 records each.
A good real‐world limit is often 5,000–10,000 phones per query to stay comfortably below 8 MB, maintain concurrency, and avoid impacting other AXL traffic. You can deep dive into this calculation in [[AXL Query Size Calculation | here]]
[!! warning:Case Study]
SXL API Restrictions
Applications using RisPort70 to perform queries on large numbers of devices, for example, to update a devicename to IP address lookup table, need to balance: Further Reading
- Request rate throttle (18 requests per minute maximum)
- The number of devices per request (up to 2000)
- The possibility of duplicate records (from
selectCmDevice)
INFO
selectCmDevice can return multiple results per unique device if a device has registered on more than one Cisco Unified CM node at some point, for example, due to fail over. selectCmDeviceExt returns only the most recent result.
- Truncated results if more than 2000 results returned
INFO
The RisPort70 response does not indicate if results greater than 2000 have been truncated.
- Keeping device state information as up-to-date as possible
[!! success:Solution]
To reduce the number of request we have use bulk request feature in API for Example:
instead of requesting one Item at a time we send it as a bulk of devices:
foreach ($chunks as $chunk) {
$sxlSelectionCriteria =
(new SXLSelectionCriteria())->setSelectItems($chunk);
$sxlResponse = $this->sxlClient->getClient($index)->SelectCmDevice("",
$sxlSelectionCriteria->toArray()
);
if ($sxlResponse['SelectCmDeviceResult']->TotalDevicesFound === 0) {
continue;
}
$cmNodes = [...$sxlResponse['SelectCmDeviceResult']->CmNodes, ...$cmNodes];
}To handle truncation of the response device we have to send above request in 500 chunks
...
$chunks = array_chunk($phoneName, 500);
foreach ($chunks as $chunk) {
$sxlSelectionCriteria =
(new SXLSelectionCriteria())->setSelectItems($chunk);
$sxlResponse = $this->sxlClient->getClient($index)->SelectCmDevice("",
$sxlSelectionCriteria->toArray()
);
...Tried to use
selectCmDeviceExtinstead ofselectCmDeviceExtbut downloadedRisPort.wsdlnot supported the operation.