5

I'm trying to do a webservice discovery using WCF's DiscoveryClient using this code:

// Setup the discovery client (WSDiscovery April 2005)
DiscoveryEndpoint discoveryEndpoint = new UdpDiscoveryEndpoint(DiscoveryVersion.WSDiscoveryApril2005);
DiscoveryClient discoveryClient = new DiscoveryClient(discoveryEndpoint);

// Setup the wanted device criteria
FindCriteria criteria = new FindCriteria();
criteria.ScopeMatchBy = new Uri("http://schemas.xmlsoap.org/ws/2005/04/discovery/rfc3986");
criteria.Scopes.Add(new Uri("onvif://www.onvif.org/"));

// Go find!
criteria.Duration = TimeSpan.FromMilliseconds(duration);
discoveryClient.FindAsync(criteria, this);

This works very well on a machine with a single IP address (10.1.4.25) assigned to the single network interface. The broadcast is sent from 10.1.4.25 to 239.255.255.250, and I get responses from 5 devices all on the same subnet.

However, when the machine has multiple IPs on the same interface, it seems to pick a single source IP and sends the request from that. In this case, I get a reply from a single device giving a 169.254 address.

I have tried setting UdpDiscoveryEndpoint.TransportSettings.MulticastInterfaceId to a suitable interface ID which hasn't helped as it identifies a single interface, not a specific IP. The UdpDiscoveryEndpoint.ListenUri property also returns the multicast address, and so won't effect the source IP. UdpDiscoveryEndpoint.Address is the URN for the discovery protocol.

Is there any way I can force it to send from a specific IP address, or ideally, multiple requests on each configured IP?

I have also tried ONVIF Device Manager that seems to have the same problem.

Note that this is not about making a service bind to a specific, or "all address" IP. It is about the IP a discovery request is sent from.

Deanna
  • 23,876
  • 7
  • 71
  • 156
  • [This page](http://msdn.microsoft.com/en-us/library/bb706924.aspx#LinkTarget_1973) mentions setting the `/s:Envelope/s:Header/a:ReplyTo` addresses but I'm not sure this can be set in WCF. – Deanna Feb 25 '14 at 11:54
  • did you ever solved this? I'm having the same problem – HypeZ Oct 22 '14 at 14:08
  • @HypeZ Nope, it's still an issue. – Deanna Oct 22 '14 at 15:34
  • Hey have either of you managed to find a solution to this? I'm pretty much doing the same and getting the same results. When I have 2 IP address my returned list drops from 30 odd devices to 5. I've check the ReplyTo field and it is correctly set to Anonymous. I've also used Wireshark and can see ALL the same replies as with one IP are being received by my NIC. Very strange! Sorry to pretty much ask the same questions as before :-) – David Ritchie Apr 14 '16 at 08:14
  • @DavidRitchie Sorry, still no solution. We just tell customers that they have to remove all bar one IP temporarily. – Deanna Apr 14 '16 at 08:20
  • 1
    Ok thanks for the reply. If I find a solution I'll be sure to post it here so we can all benefit – David Ritchie Apr 14 '16 at 08:22
  • Hi, I have the same problem. Did you find any solution for this issue? – aminexplo Oct 09 '16 at 07:31
  • @aminexplo Still nope. Sorry :( – Deanna Oct 25 '16 at 14:09
  • @Deanna Does the solution that i submitted work for you? Could you please test it? – aminexplo Oct 31 '16 at 04:56
  • @aminexplo Sorry, I'm no longer working on that project so not in a position to test. Your idea was something I thought about but hadn't had time to implement, essentially bypassing the `DiscoveryClient` entirely. – Deanna Nov 30 '16 at 10:29

1 Answers1

3

Well, I had the same problem and after some days of research, reading ONVIF documents and learning some tips about multicasting, I developed this code which works fine. As an example the main IP address on my network adapter is 192.168.80.55 and I also set another IP(192.168.0.10) in advanced settings. With use of this code I can discover device service of a camera with the IP address of 192.168.0.12. The most important part of this sample is "DeepDiscovery" method which contains the main idea of iteration on network addresses and multicasting proper Probe message. I recommend deserialization of the response in "GetSocketResponse" method. Currently, just I extract the service URI using Regex.

As mentioned in this article (https://msdn.microsoft.com/en-us/library/dd456791(v=vs.110).aspx):

For WCF Discovery to work correctly, all NICs (Network Interface Controller) should only have 1 IP address.

I am doing the exact action that WS-Discovery does and using the standard 3702 port, but I myself build the SOAP envelope and use Socket class for sending the packet for all IP addresses that have been set for the network interface controller.

class Program
{
    static readonly List<string> addressList = new List<string>();
    static readonly IPAddress multicastAddress = IPAddress.Parse("239.255.255.250");
    const int multicastPort = 3702;
    const int unicastPort = 0;

    static void Main(string[] args)
    {
        DeepDiscovery();
        Console.ReadKey();
    }

    public static void DeepDiscovery()
    {
        string probeMessageTemplate = @"<s:Envelope xmlns:s=""http://www.w3.org/2003/05/soap-envelope"" xmlns:a=""http://schemas.xmlsoap.org/ws/2004/08/addressing""><s:Header><a:Action s:mustUnderstand=""1"">http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe</a:Action><a:MessageID>urn:uuid:{messageId}</a:MessageID><a:ReplyTo><a:Address>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address></a:ReplyTo><a:To s:mustUnderstand=""1"">urn:schemas-xmlsoap-org:ws:2005:04:discovery</a:To></s:Header><s:Body><Probe xmlns=""http://schemas.xmlsoap.org/ws/2005/04/discovery""><d:Types xmlns:d=""http://schemas.xmlsoap.org/ws/2005/04/discovery"" xmlns:dp0=""http://www.onvif.org/ver10/device/wsdl"">dp0:Device</d:Types></Probe></s:Body></s:Envelope>";

        foreach (IPAddress localIp in
            Dns.GetHostAddresses(Dns.GetHostName()).Where(i => i.AddressFamily == AddressFamily.InterNetwork))
        {
            var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            socket.Bind(new IPEndPoint(localIp, unicastPort));
            socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(multicastAddress, localIp));
            socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, 255);
            socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
            socket.MulticastLoopback = true;
            var thread = new Thread(() => GetSocketResponse(socket));
            var probeMessage = probeMessageTemplate.Replace("{messageId}", Guid.NewGuid().ToString());
            var message = Encoding.UTF8.GetBytes(probeMessage);
            socket.SendTo(message, 0, message.Length, SocketFlags.None, new IPEndPoint(multicastAddress, multicastPort));
            thread.Start();
        }
    }


    public static void GetSocketResponse(Socket socket)
    {
        try
        {
            while (true)
            {
                var response = new byte[3000];
                EndPoint ep = socket.LocalEndPoint;
                socket.ReceiveFrom(response, ref ep);
                var str = Encoding.UTF8.GetString(response);
                var matches = Regex.Matches(str, @"http://\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/onvif/device_service");
                foreach (var match in matches)
                {
                    var value = match.ToString();
                    if (!addressList.Contains(value))
                    {
                        Console.WriteLine(value);
                        addressList.Add(value);
                    }
                }
                //...
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex);
            //...
        }
    }
}
aminexplo
  • 360
  • 1
  • 13
  • This looks reasonable, but I'm no longer in a position to test this. Thanks for the response though. – Deanna May 10 '17 at 10:07