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);
}
}
}
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.