Search code examples
udpmulticastupnpudpclient

Why can't I get UPnP unicast M-SEARCH to work instead of MultiCast M-SEARCH?


Good morning,

We've decided to use UPnP as much as possible. We are using MultiCast on 239.255.255.250:1900 for our M-SEARCH.

However, we're looking at how to handle when a customer has MultiCast locked down on their network. Looking at the UPnP 1.1 spec, it talks about using a unicast with M-SEARCH. So, if we already know the IP addresses of the various devices we want to talk to, and they are listening on 0.0.0.0:1900, we're thinking we could send a unicast M-SEARCH to each device on deviceIP:1900.

I've been trying to do this and am having one heck of a time getting the devices to receive and respond to the unicast M-SEARCH request.

First, is it allowed have your first UPnP conversation with a device start with a unicast M-SEARCH?

Second, is there some reason that listening on 0.0.0.0:1900 wouldn't accept a message sent to the deviceIP:1900?

When I do a netstat on my machine to see what IPs and Ports are in use, it appears that either 239.255.255.250:1900 is not on the list, or that it is appearing as 0.0.0.0:1900.

So, if 0.0.0.0 is (ANY_IP) then would having a single listener listening on 0.0.0.0:1900 be sufficient to receive any messages MultiCast to 239.255.255.250:1900 and any sent directly via unicast to that machine's ip:1900?

When testing, I'm able to always receive MultiCasts, but I never receive unicasts for M-SEARCH. I am able to communicate with devices on their other ports when doing a GET and such, but just seems like I can't get port 1900 to respond to unicast M-SEARCH.

Can you actually listen on 239.255.255.250:1900 as multicast and listen on 0.0.0.0:1900 as unicast at the same time on the same machine without a udp socket conflict?

Any advice and pointers on this would be greatly appreciated.

Thanks, Curtis

PS: The code I'm using is below. For address in the constructor, we're passing in IPAddress.Any (which is 0.0.0.0) and Protocol.Port is 1900. This is running on a windows machine under Windows 8.1:

//
// SsdpSocket.cs
//
// Author:
//   Aaron Bockover <abockover@novell.com>
//
// Copyright (C) 2008 Novell, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//

using System;
using System.Net;
using System.Net.Sockets;

namespace Mono.Ssdp.Mono.Ssdp.Internal
{
    class SsdpSocket : Socket
    {
        static readonly IPEndPoint ssdp_send_point = new IPEndPoint (Protocol.IPAddress, Protocol.Port);

        readonly IPEndPoint ssdp_receive_point;

        public SsdpSocket (IPAddress address)
            : base (AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp)
        {
            ssdp_receive_point = new IPEndPoint (address, Protocol.Port);
            SetSocketOption (SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
        }

        public IAsyncResult BeginSendTo (byte [] data, AsyncCallback callback)
        {
            return BeginSendTo (data, callback, ssdp_send_point);
        }

        public IAsyncResult BeginSendTo (byte[] data, AsyncCallback callback, IPEndPoint endPoint)
        {
            return BeginSendTo (data, 0, data.Length, SocketFlags.None, endPoint, callback, this);
        }

        public IAsyncResult BeginReceiveFrom (AsyncReceiveBuffer buffer, AsyncCallback callback)
        {
            return base.BeginReceiveFrom (buffer.Buffer, 0, buffer.Buffer.Length, SocketFlags.None, 
                ref buffer.SenderEndPoint, callback, buffer);
        }

        public void Bind ()
        {
            Bind (ssdp_receive_point);
        }
    }
}

Solution

  • The fix for this was in the validation of the Unicast message.

    Here are two examples of messages. The first is a MultiCast, the second is a UniCast:

    M-SEARCH * HTTP/1.1
    HOST: 239.255.255.250:1900 
    MAN: "ssdp:discover" 
    MX: seconds to delay response 
    ST: search target 
    USER-AGENT: OS/version UPnP/1.1 product/version
    
    
    
    M-SEARCH * HTTP/1.1
    HOST: hostname:portNumber
    MAN: "ssdp:discover"
    ST: search target
    USER-AGENT: OS/version UPnP/1.1 product/version
    

    Please note, the second M-SEARCH is a unicast search and is NOT required to have the 'MX:' line in it. The code I was using was requiring the MX: line and using its value. If there was no MX: line, we were getting an exception that was quietly hidden away and gobbled up by a catch(exception){}

    Anyway, the UPnPServer just listens on 0.0.0.0:1900 and will hear all msearches that are Multi-Cast and that are Unicast.