我正在尝试实现SSDP协议,但我不确定它到底是如何工作的。SSDP通过udp发送数据,这是显而易见的。若控制器连接到网络,它可以搜索带有MSEARCH消息的设备,该消息可以发送到多播地址239.255.255.250:1900。每个设备都必须监听此地址并做出响应。但我不知道他们是怎么回应的。我在wireshark中看到他们用单播进行响应,但我不知道如何确定接收响应的端口。
编辑---------------
我正在尝试用spike-fuzzing框架编写ssdp-fuzzer。正如我所说,我能够发送正确的数据,但无法收到回复。我将尝试粘贴一些简短解释的尖峰代码。有一个Spike struct,它表示要发送的数据(它存储实际数据、大小、协议信息…)。我删除了一些变量以使其更加清晰。
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 targetn");
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 packetn");
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 readn");
}
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:%dn",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不生成数据。。。
为了实现SSDP,您的应用程序需要能够将NOTIFY
和M-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:
问题是关于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()
,这是用于连接的资源的通用POSIX函数(当描述符是持久的时)。一般来说,在我看来,您以TCP客户端为例,只是更改了协议。它不适用于UDP。若要接收UDP数据包,必须在您一侧设置一个服务器。UDP不知道某些数据包是否像TCP一样"是对其他数据包的响应"。UDP是无连接,但您应该已经知道了。
我建议阅读通用UDP原则,尝试在UDP中实现非常简单的回声服务器(随机的良好起点),然后在上面堆积SSDP多播。