SSDP protocol - implementation
Asked Answered
F

2

8

I am trying to implement SSDP protocol, but I am not sure how exactly it works. SSDP sends data over udp, that is clear. If controller connects to network it can search for devices with MSEARCH message, which can be send to multicast address 239.255.255.250:1900. Every device has to listen to this address and respond. But I don't know how they responds. I see in wireshark that they responds with unicast but I don't know how to determine port which receives responds.

EDIT---------------

I am trying to write ssdp fuzzer with spike fuzzing framework. As I said, I am able to send correct data, but unable to receive responses. I ll try to paste some spike code with brief explanation. There is Spike struct, which represents data to be send (it stores actual data, sizes, protocol info...). I removed some variables to make it more clear.

struct spike {

  /*total size of all data*/
  unsigned long datasize;
  unsigned char *databuf;
  unsigned char *endbuf;
  int fd; /*for holding socket or file information*/
  int proto; /*1 for tcp, 2 for udp*/
  struct sockaddr_in *destsockaddr;
};

Now i am sending data via udp and want to receive some responds with following functions

spike_connect_udp(target,port);
spike_send();
s_read_packet(); 

Function implementations:

int 
spike_connect_udp(char * host, int port)
{
  int fd;
  /*ahh, having udpstuff.c makes this stuff easy*/
  fd=udpconnect(host,port);
  if (fd==-1)
    {
      fprintf(stderr,"Couldn't udp connect to target\n");
      return (0);
    }
  current_spike->fd=fd;
  current_spike->proto=2; /*UDP*/
  return 1;
}

int
udpconnect(const char * host,  const unsigned short port )
{
  int sfd = -1;
  struct sockaddr_in addr;
  /* Translate hostname from DNS or IP-address form */

  memset(&addr, 0, sizeof(addr));
  if (!getHostAddress(host, &addr))
    {
      hdebug("can't resolve host or address.\n");
      return -1;
    }
  addr.sin_family = AF_INET;
  addr.sin_port = ntohs(port);

  if ((sfd = socket(AF_INET,  SOCK_DGRAM, 0)) < 0)
    {
      hdebug("Could not create socket!\n");
      return -1;
    }

  /* Now connect! */

  if (connect(sfd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
    {
      close(sfd);
      return -1;
    }

  return sfd;

}

int
spike_send()
{
  int retval;

  switch (current_spike->proto) 
    {
    case 1: /*TCP*/
    //deleted, doesnt matter, i am sending via udp
    case 2: /*UDP*/
      //udp_write_data is function from framework
      retval=udp_write_data(current_spike->fd, current_spike->destsockaddr, s_get_size(), s_get_databuf());
      break;

    }

  fflush(0);

  return retval;
}

This works fine and send data via udp. Now i would like to receive some responses via open socket current_spike->fd. Function s_read_packet void

s_read_packet()
{
  unsigned char buffer[5000];
  int i;
  int size;
  s_fd_wait();
  printf("Reading packet\n");
  memset(buffer,0x00,sizeof(buffer));
  /what alarm and fcntl does?
  alarm(1);
  fcntl(current_spike->fd, F_SETFL, O_NONBLOCK);
  //this read return error -1 and sets errno to 11 service temporarily unavailable
  size=read(current_spike->fd,buffer,1500);
  fcntl(current_spike->fd, F_SETFL, 0);
  alarm(0);

  for (i=0; i<size; i++)
    {
      if (isprint(buffer[i]))
    printf("%c",buffer[i]);
      else
    printf("[%2.2x]",buffer[i]);
    }

  printf("\nDone with read\n");
}

int
s_fd_wait()
{
  /*this function does a select to wait for
    input on the fd, and if there
    is, returns 1, else 0 */
  int fd;
  fd_set rfds;
  struct timeval tv;
  int retval;

  fd=current_spike->fd;

  /* Watch server_fd (fd 0) to see when it has input. */
  FD_ZERO(&rfds);
  FD_SET(fd, &rfds);
  /* Wait up to zero seconds  .  will this wait forever? not on linux.*/

  /* from man page: timeout is an upper bound on the amount of time
       elapsed before select returns. It may be zero, causing select
       to return immediately.  If timeout is NULL (no timeout), select
       can block indefinitely. */

  /*wait 2 seconds only*/
  tv.tv_sec = TIMEINSECONDS;
  tv.tv_usec = TIMEINUSECONDS;
  //printf("Before select %d:%d\n",TIMEINSECONDS,TIMEINUSECONDS);
  retval = select(fd+1, &rfds, NULL, NULL, &tv);
  /* Don't rely on the value of tv now! */
  //printf("After select retval=%d.\n",retval);
  switch (retval)
   {
     case 0:
       /*Timeout - no packet or keypress*/
        return(0);
        break;
     case -1:
      /* ignore interrupted system calls */
       if (errno != EINTR)
         {
           /*some kind of weird select error. Die. */
           exit(-1);
         }
      /*otherwise we got interrupted, so just return false*/
       return (0);
       break;
     default:
         {
           if (FD_ISSET(fd,&rfds))
             return (1);
           else
            return (0);
         }
   }
} 

But function s_read_packet produces no data...

Fullfaced answered 14/11, 2012 at 16:3 Comment(0)
V
12

In order to implement the SSDP your application needs to be able to send the NOTIFY and M-SEARCH header to the designated multicast address and also should be able to receive these messages. In order to do so you will need to create a specialized UDP Socket.

Here is an example on how to initialize such a socket:

// Structs needed
struct in_addr localInterface;
struct sockaddr_in groupSock;
struct sockaddr_in localSock;
struct ip_mreq group;   

// Create the Socket
int udpSocket = socket(AF_INET, SOCK_DGRAM, 0);

// Enable SO_REUSEADDR to allow multiple instances of this application to receive copies of the multicast datagrams.
int reuse = 1;

setsockopt(udpSocket, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse, sizeof(reuse));

// Initialize the group sockaddr structure with a group address of 239.255.255.250 and port 1900.
memset((char *) &groupSock, 0, sizeof(groupSock));

groupSock.sin_family = AF_INET;
groupSock.sin_addr.s_addr = inet_addr("239.255.255.250");
groupSock.sin_port = htons(1900);

// Disable loopback so you do not receive your own datagrams.
char loopch = 0;

setsockopt(udpSocket, IPPROTO_IP, IP_MULTICAST_LOOP, (char *)&loopch, sizeof(loopch));

// Set local interface for outbound multicast datagrams. The IP address specified must be associated with a local, multicast capable interface.
localInterface.s_addr = inet_addr("192.168.0.1");

setsockopt(udpSocket, IPPROTO_IP, IP_MULTICAST_IF, (char *)&localInterface, sizeof(localInterface));

// Bind to the proper port number with the IP address specified as INADDR_ANY.
memset((char *) &localSock, 0, sizeof(localSock));
localSock.sin_family = AF_INET;
localSock.sin_port = htons(1900);
localSock.sin_addr.s_addr = INADDR_ANY;

bind(udpSocket, (struct sockaddr*)&localSock, sizeof(localSock));

// Join the multicast group on the local interface. Note that this IP_ADD_MEMBERSHIP option must be called for each local interface over which the multicast datagrams are to be received.
group.imr_multiaddr.s_addr = inet_addr("239.255.255.250");
group.imr_interface.s_addr = inet_addr("192.168.0.1");

setsockopt(udpSocket, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *)&group, sizeof(group));

Now you can use this socket to send your Data to the multicast group with:

sendto(udpSocket, message, message_length, 0, (struct sockaddr*)&groupSock, sizeof(groupSock));

To receive messages do:

struct sockaddr_in si_other;
socklen_t slen = sizeof(si_other);
char buffer[1024];

recvfrom(udpSocket, buffer, 1024, 0, (struct sockaddr *) &si_other, &slen);

To respond to a specific request (received as above) do:

sendto(udpSocket, message, message_length, 0, (struct sockaddr*)&si_other, sizeof(si_other));

All you have do to now is to create the needed Messages to send and to process the received data. Let's say you send a M-SEARCH request to the multicast group (as mentioned above) then you would get a response like this from each device:

HTTP/1.1 200 OK
SERVER: Linux/2.6.15.2 UPnP/1.0 Mediaserver/1.0
CACHE-CONTROL: max-age=1200
LOCATION: http://192.168.0.223:5001/description.xml 
ST: urn:schemas-upnp-org:device:MediaServer:4
USN: uuid:550e8400-e29b-11d4-a716-446655440000::urn:schemas-upnp-org:device:MediaServer:4
Content-Length: 0
EXT:
Visual answered 19/12, 2014 at 13:0 Comment(1)
Great answer, even after all these years. Thanks a lot.Putman
A
6

The question is about general principle of TCP/UDP communication, not as much about SSDP specifics. If controller as an UDP network client opens a socket to a particular remote address (doesn't matter whether multicast or unicast), the local address is an applicable local network adapter address and some port number, assigned by OS. It appears random, but OS assigns it carefully to manage uniqueness for all applications using the same network adapter. In Wireshark, you'll see something like:

IP, Src: 192.168.1.40 Dst: 239.255.255.250
UDP, Src Port: 42578 Dst Port: 1900

Where 192.168.1.40 is (outgoing) network address of the controller. The device will have to respond to 192.168.1.40:42578. UDP/IP stack implementation gives you that tuple.

I recommend reading UPnP Device Architecture document. Chapter 1 is all about SSDP and exactly the part you are asking: discovery, advertisement and searching.

Edit after code added:

I don't see any "server" code, any bind(). You are trying to use the same descriptor for sending as well as receiving. Furthermore, you are using read() which is a generic POSIX function for connected resources (when the descriptor is persistent). In general, it looks to me like you have taken TCP client example and just changed the protocol. It won't work with UDP. To receive UDP packets, you must set up a server on your side. UDP doesn't know if some packet "is a response" to some other packet, as with TCP. UDP is connectionless, but you should already know that.

I recommend reading up on generic UDP principles, try implement very simple echo server in UDP (random good looking start point), and only then pile up SSDP multicasting on it.

Adamite answered 14/11, 2012 at 22:13 Comment(3)
ty for response, i pasted some code to my question, it would be great if u can look at it :)Fullfaced
Just an aside: the best WireShark filter I have found to see just SSDP packets is this: (udp contains "HTTP/1.1") and ((udp contains 0a:53:54:3a) or (udp contains 0a:59:54:3a))Tuff
Note that since Wireshark 2.2, one can simply use ssdp as a display filter.Blueberry

© 2022 - 2024 — McMap. All rights reserved.