Security Camera ONVIF Discovery

The ONVIF specification standardises the network interface (the network layer) of network video products. It defines a communication framework based on relevant IETF and Web Services standards including security and IP configuration requirements. ONVIF uses Web Services Dynamic Discovery (WS-Discovery) to locate devices on the local network which operates over UDP port 3702 and uses IP multicast address 239.255.255.250.

The first issue was that WS-Discovery is not currently supported by the .Net Core Windows Communication Foundation(WCF) implementation CoreWCF(2021-08). So I built a proof of concept(PoC) client which used UDP to send and receive XML messages (WS-Discovery specification) to “probe” the local network.

My .Net Core 5 console application enumerates the host device’s network interfaces, then sends a “probe” message and waits for responses. The ONVID application programmers guide specifies the format of the “probe” request and response messages (One of the namespace prefixes in the sample is wrong). The client device can return its name and details of it’s capabilities in the response. Currently I only need the IP addresses of the cameras but if more information was required I would use the XML Serialisation functionality of .Net Core to generate the requests and unpack the responses.

class Program
{
	// From https://specs.xmlsoap.org/ws/2005/04/discovery/ws-discovery.pdf & http://www.onvif.org/wp-content/uploads/2016/12/ONVIF_WG-APG-Application_Programmers_Guide-1.pdf
	const string WSDiscoveryProbeMessages =
		"<?xml version = \"1.0\" encoding=\"UTF-8\"?>" +
		"<e:Envelope xmlns:e=\"http://www.w3.org/2003/05/soap-envelope\" " +
			"xmlns:w=\"http://schemas.xmlsoap.org/ws/2004/08/addressing\" " +
			"xmlns:d=\"http://schemas.xmlsoap.org/ws/2005/04/discovery\" " +
			"xmlns:dn=\"http://www.onvif.org/ver10/network/wsdl\"> " +
				"<e:Header>" +
					"<w:MessageID>uuid:{0}</w:MessageID>" +
					"<w:To e:mustUnderstand=\"true\">urn:schemas-xmlsoap-org:ws:2005:04:discovery</w:To> " +
					"<w:Action mustUnderstand=\"true\">http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe</w:Action> " +
				"</e:Header> " +
				"<e:Body> " +
					"<d:Probe> " +
						"<d:Types>dn:NetworkVideoTransmitter</d:Types>" +
					"</d:Probe> " +
				"</e:Body> " +
		"</e:Envelope>";

	static async Task Main(string[] args)
	{
		List<UdpClient> udpClients = new List<UdpClient>();

		foreach (var networkInterface in NetworkInterface.GetAllNetworkInterfaces())
		{
			Console.WriteLine($"Name {networkInterface.Name}");
			foreach (var unicastAddress in networkInterface.GetIPProperties().UnicastAddresses)
			{
				if (unicastAddress.Address.AddressFamily == AddressFamily.InterNetwork)
				{
					var udpClient = new UdpClient(new IPEndPoint(unicastAddress.Address, 0)) { EnableBroadcast = true };

					udpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, 5000);

					udpClients.Add(udpClient);
				}
			}
		}

	var multicastEndpoint = new IPEndPoint(IPAddress.Parse("239.255.255.250"), 3702);

		foreach (UdpClient udpClient in udpClients)
		{
			byte[] message = UTF8Encoding.UTF8.GetBytes(string.Format(WSDiscoveryProbeMessages, Guid.NewGuid().ToString()));

			try
			{
				await udpClient.SendAsync(message, message.Length, multicastEndpoint);

				IPEndPoint remoteEndPoint = null;

				while(true)
				{				
					message = udpClient.Receive(ref remoteEndPoint);

					Console.WriteLine($"IPAddress {remoteEndPoint.Address}");
					Console.WriteLine(UTF8Encoding.UTF8.GetString(message));

					Console.WriteLine();
				}
			}
			catch (SocketException sex)
			{
				Console.WriteLine($"Probe failed {sex.Message}");
			}
		}

		Console.WriteLine("Press enter to <exit>");
		Console.ReadKey();
	}
}

After confirming the program was working I used the excellent RaspberryDebugger to download the application and debug it on a Raspberry PI 3 running the Raspberry PI OS.