In network programming, discovering UPnP (Universal Plug and Play) devices is crucial for various applications, including home automation, media streaming, and network monitoring.
The UPnP discovery process involves broadcasting search requests and parsing responses to identify available devices on the network.
Recently, I came accross a very usefull article related with UPnP device discovery. After checking the code, I believed I could improve itupdating the UPnP device scanning function to enhance efficiency, ensure correct resource management, and optimize result handling.
You can check the complete article and source code here.
Key Improvements
The latest updates introduce several enhancements, including:
✅ The function now returns a Hashtable containing unique IP addresses.
✅ Uses HashSet to filter out duplicate IPs before converting them into a Hashtable.
✅ Extracts valid IP addresses from the response instead of printing the entire buffer.
✅ Resets the scan timer only when a response is received to avoid false timeouts.
✅ Closes the socket properly at the end of the scan.
Updated Synchronous Version
Here’s the improved synchronous version of the function:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions;
static Hashtable ScanUPnPDevicesNew()
{
IPEndPoint localEndPoint = new IPEndPoint(IPAddress.Parse(utilities.GetLocalIPAddress()), 1901);
IPEndPoint multicastEndPoint = new IPEndPoint(IPAddress.Parse("239.255.255.250"), 1900);
Socket udpSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
udpSocket.Bind(localEndPoint);
string searchString = "M-SEARCH * HTTP/1.1\r\n" +
"HOST: 239.255.255.250:1900\r\n" +
"MAN: \"ssdp:discover\"\r\n" +
"ST: ssdp:all\r\n" +
"MX: 3\r\n\r\n";
udpSocket.SendTo(Encoding.UTF8.GetBytes(searchString), SocketFlags.None, multicastEndPoint);
byte[] receiveBuffer = new byte[64000];
HashSet<string> foundIps = new HashSet<string>();
Stopwatch scanTimer = Stopwatch.StartNew();
while (scanTimer.ElapsedMilliseconds < 5000)
{
if (udpSocket.Available > 0)
{
int receivedBytes = udpSocket.Receive(receiveBuffer, SocketFlags.None);
if (receivedBytes > 0)
{
scanTimer.Restart();
string response = Encoding.UTF8.GetString(receiveBuffer, 0, receivedBytes);
Match match = Regex.Match(response, @"LOCATION:\s*http://([\d\.]+)", RegexOptions.IgnoreCase);
if (match.Success)
{
string ipAddress = match.Groups[1].Value;
if (foundIps.Add(ipAddress))
{
Console.WriteLine($"Device found at IP: {ipAddress}");
}
}
}
}
}
udpSocket.Close();
Hashtable ipTable = new Hashtable();
int index = 0;
foreach (var ip in foundIps)
{
ipTable.Add(index++, ip);
}
return ipTable;
}
Updated Asynchronous Version
If you need an asynchronous UPnP scanning function here is my idea.
It’s not the best code ever, but it does the job pretty well.
This version makes it non-blocking and more efficient, especially for applications requiring real-time responsiveness.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
static async Task<HashSet<string>> ScanUPnPDevicesNew()
{
IPEndPoint localEndPoint = new IPEndPoint(IPAddress.Parse(utilities.GetLocalIPAddress()), 1901);
IPEndPoint multicastEndPoint = new IPEndPoint(IPAddress.Parse("239.255.255.250"), 1900);
using (Socket udpSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp))
{
udpSocket.Bind(localEndPoint);
string searchString = "M-SEARCH * HTTP/1.1\r\n" +
"HOST: 239.255.255.250:1900\r\n" +
"MAN: \"ssdp:discover\"\r\n" +
"ST: ssdp:all\r\n" +
"MX: 3\r\n\r\n";
udpSocket.SendTo(Encoding.UTF8.GetBytes(searchString), SocketFlags.None, multicastEndPoint);
byte[] receiveBuffer = new byte[64000];
HashSet<string> foundIps = new HashSet<string>();
Stopwatch scanTimer = Stopwatch.StartNew();
while (scanTimer.ElapsedMilliseconds < 5000)
{
if (udpSocket.Available > 0)
{
int receivedBytes = udpSocket.Receive(receiveBuffer, SocketFlags.None);
if (receivedBytes > 0)
{
scanTimer.Restart();
string response = Encoding.UTF8.GetString(receiveBuffer, 0, receivedBytes);
Match match = Regex.Match(response, @"LOCATION:\s*http://([\d\.]+)", RegexOptions.IgnoreCase);
if (match.Success)
{
string ipAddress = match.Groups[1].Value;
if (foundIps.Add(ipAddress))
{
Console.WriteLine($"Device found at IP: {ipAddress}");
}
}
}
}
await Task.Delay(10); // Prevents CPU overuse
}
return foundIps;
}
}
How to Use the Updated Function
To call the synchronous function:
Hashtable nvrIps = ScanUPnPDevicesNew();
if (nvrIps.Count == 0)
{
Console.WriteLine("No NVR devices found.");
}
else
{
foreach (DictionaryEntry entry in nvrIps)
{
Console.WriteLine($"Device found at IP: {entry.Value}");
}
}
To call the asynchronous function:
static async Task Main()
{
HashSet<string> nvrIps = await ScanUPnPDevicesNew();
if (nvrIps.Count == 0)
{
Console.WriteLine("No NVR devices found.");
}
else
{
Console.WriteLine($"Found {nvrIps.Count} devices:");
foreach (var ip in nvrIps)
{
Console.WriteLine($"- {ip}");
}
}
}
With these improvements, UPnP device scanning is now more efficient, accurate, and asynchronous-friendly, making it suitable for modern applications.
Leave a Reply