7

我正在尝试实现 SSDP 协议,但我不确定它是如何工作的。SSDP 通过 udp 发送数据,这很清楚。如果控制器连接到网络,它可以搜索带有 MSEARCH 消息的设备,该消息可以发送到多播地址 239.255.255.250:1900。每个设备都必须监听这个地址并做出响应。但我不知道他们是如何回应的。我在wireshark中看到他们用单播响应,但我不知道如何确定接收响应的端口。

编辑 - - - - - - - -

我正在尝试使用尖峰模糊测试框架编写 ssdp 模糊测试。正如我所说,我能够发送正确的数据,但无法接收响应。我将尝试粘贴一些带有简要说明的尖峰代码。有 Spike 结构,它表示要发送的数据(它存储实际数据、大小、协议信息......)。我删除了一些变量以使其更清晰。

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

现在我正在通过 udp 发送数据,并希望通过以下功能接收一些响应

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

功能实现:

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

这工作正常并通过 udp 发送数据。现在我想通过打开的套接字 current_spike->fd 接收一些响应。函数 s_read_packet 无效

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

但是函数 s_read_packet 不会产生任何数据...

4

2 回答 2

11

为了实现 SSDP,您的应用程序需要能够将NOTIFYandM-SEARCH标头发送到指定的多播地址,并且还应该能够接收这些消息。为此,您需要创建一个专门的 UDP 套接字。

下面是一个关于如何初始化这样一个套接字的例子:

// 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));

现在您可以使用此套接字将您的数据发送到多播组:

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

要接收消息,请执行以下操作:

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

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

要响应特定请求(如上接收),请执行以下操作:

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

您现在所要做的就是创建所需的消息以发送和处理接收到的数据。假设您向M-SEARCH多播组发送请求(如上所述),然后您会从每个设备收到如下响应:

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:
于 2014-12-19T13:00:10.307 回答
6

问题是关于 TCP/UDP 通信的一般原则,而不是关于 SSDP 的细节。如果控制器作为 UDP 网络客户端打开一个到特定远程地址的套接字(无论是多播还是单播),则本地地址是适用的本地网络适配器地址和一些端口号,由操作系统分配。它看起来是随机的,但操作系统会仔细分配它以管理使用同一网络适配器的所有应用程序的唯一性。在 Wireshark 中,您会看到如下内容:

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

192.168.1.40控制器的(传出)网络地址在哪里。设备必须响应192.168.1.40:42578。UDP/IP 堆栈实现为您提供了该元组。

我建议阅读UPnP 设备架构文档。第 1 章是关于 SSDP 的全部内容,也正是您要问的部分:发现、广告和搜索。

添加代码后编辑:

我没有看到任何“服务器”代码,任何bind(). 您正在尝试使用相同的描述符进行发送和接收。此外,您正在使用read()which 是连接资源的通用 POSIX 函数(当描述符是持久的时)。一般来说,在我看来,您以 TCP 客户端为例,只是更改了协议。它不适用于UDP。要接收 UDP 数据包,您必须在您身边设置一个服务器。UDP 不知道某个数据包是否是对某个其他数据包的“响应”,就像 TCP 一样。UDP 是无连接的,但你应该已经知道了。

我建议阅读通用UDP 原理,尝试在 UDP 中实现非常简单的回显服务器(随机美观的起点),然后在其上堆积 SSDP 多播。

于 2012-11-14T22:13:03.437 回答