2013年10月16日 星期三

如何由收到的封包得知是 unicast 或是 multicast ?

ONVIF 使用 WS-Discovery 作為其尋找設備的規範,並且對Discovery作了點小修改。
其中主要的差異便是針對 unicast/multicast probe,其錯誤處理不同。 

  • 當收到 multicast probe時,若發現此封包有任何錯誤時,都不回錯誤。 
  • 當收到 unicast probe時,若發現此封包的 MatchBy 有錯誤時,需回錯誤。 

原文摘錄如下:
If an error exists with the multicast packet, the device and client should silently discard and ignore the request. Sending an error response is not recommended due to the possibility of packet storms if many devices send an error response to the same request. For completeness, unicast packet error handling is described below. 
If a device receives a unicast Probe message and it does not support the matching rule, then the device may choose not to send a Probe Match, and instead generate a SOAP fault bound to SOAP 1.2 as follows: 
[action] http://schemas.xmlsoap.org/ws/2005/04/discovery/fault  
[Code]
s12:Sender
[Subcode] d:MatchingRuleNotSupported
[Reason] E.g., the matching rule specified is not supported  
[Detail]
List of xs:anyURI

因此在實作WS-Discovery 時,便需要由收到的封包得知對方是送往 unicast address 或是 multicast address,以決定不同的處理方式?

經過一天的測試之後,發現有兩個方法,整理如下:

1. 使用 SOCK_RAW 來建立 socket,當收到封包之後,自行解析 ip header。
參考下圖,IP Header的前 20Bytes為固定欄位,其中 Bytes 12-15 為 Source Address,Bytes 16-19 為 Destination Address 
因此使用 raw socket 接收資料時,只要把 buf[12-15] 以及 buf[16-19] 取出即可。例如:
int sourceport=ntohs(*(short *)(buf + 20));
int destport=ntohs(*(short *)(buf + 22));  

2. 使用 SOCK_DGRAM 來建立 socket,透過 setsockopt 要求回覆 IP_PKTINFO,接著使用 recvmsg 接收封包,從中取得來源與目的地 IP Address。程式碼舉例如下:

 1 // Note: 
 2 // This is a demo program to get the destination address of received packet.
 3 // You can use ONVIF Test Tool as a client to send unicast/multicast packet.
 4 #include <stdio.h>
 5 #include <time.h>
 6 #include <string.h>
 7 #include <sys/types.h>
 8 #include <sys/socket.h>
 9 #include <arpa/inet.h>
10 #include <netinet/in.h>
11 #include <ifaddrs.h>
12 #include <unistd.h>
13 #include <sys/ioctl.h>
14 #include <net/if.h>
15 #include <netdb.h>
16 #define MULTICAST_ADDR "239.255.255.250"
17 #define MULTICAST_PORT 3702
18 #define NET_MAX_INTERFACE 4
19 
20 char gpLocalAddr[NET_MAX_INTERFACE][32]={{0}};
21 char * initMyIpString(void);
22 int CreateMulticastServer(char *pAddress, int port);
23 
24 int main(int argc, char **argv)
25 {
26    int msocket_srv = 0;
27    int vReciveLen=0;
28  
29    initMyIpString();
30    msocket_srv = CreateMulticastServer(MULTICAST_ADDR, MULTICAST_PORT);
31             
32    while(1)
33    {
34       char cmbuf[1024], iovbuf[10240];
35       struct sockaddr_in localaddr;
36       struct iovec iov[1];
37 
38       localaddr.sin_family = AF_INET;
39       localaddr.sin_addr.s_addr = inet_addr(gpLocalAddr[0]);
40       iov[0].iov_base = iovbuf;
41       iov[0].iov_len = sizeof(iovbuf);
42       
43       struct msghdr mh = {
44          .msg_name = &localaddr,
45          .msg_namelen = sizeof(localaddr),
46          .msg_control = cmbuf,
47          .msg_controllen = sizeof(cmbuf),
48          .msg_iov = iov,
49          .msg_iovlen = 1,
50       };
51       
52       printf("Waiting Data...\n");
53       vReciveLen = recvmsg(msocket_srv, &mh, 0);
54       if(vReciveLen<0) printf("Receive Message... \nMessage Len < 0 !!!\n");
55       else printf("Receive Message... \nMessage Len = %d \n", vReciveLen);
56       //printf("Data Length=%d\n Data=\n%s\n", vReciveLen, iovbuf);
57       
58       struct cmsghdr *cmsg = NULL;      
59       struct in_pktinfo *pi = NULL;           
60       for(cmsg = CMSG_FIRSTHDR(&mh) ;
61           cmsg != NULL;
62           cmsg = CMSG_NXTHDR(&mh, cmsg))
63       {
64          if(cmsg->cmsg_level != IPPROTO_IP || cmsg->cmsg_type != IP_PKTINFO)
65             continue;
66 
67          pi = (struct in_pktinfo *)CMSG_DATA(cmsg); 
68          if(pi)
69          {
70             char *pTmp, pSrc[32]={0}, pDst[32]={0};
71 
72             // inet_ntoa() use a global buffer to store the string,
73             // so we need to copy the value before we invoke inet_ntoa() next time        
74             if( (pTmp = inet_ntoa(pi->ipi_addr)) != NULL)
75                memcpy(pDst, pTmp, strlen(pTmp));
76             if( (pTmp = inet_ntoa(localaddr.sin_addr)) != NULL)
77                memcpy(pSrc, pTmp, strlen(pTmp));
78               
79             printf("nIndex=%d pSrc=%s:%d, pDst=%s\n", pi->ipi_ifindex, pSrc, ntohs(localaddr.sin_port), pDst);
80             if(strncmp(pDst, MULTICAST_ADDR, strlen(MULTICAST_ADDR))==0)
81                printf("The receive packet is send to host's multicast address\n");
82             else
83                printf("The receive packet is send to host's unicast address\n");
84             break;
85          }
86       } 
87    }
88    
89    close(msocket_srv);
90    return 1;
91 }
92 
93 int CreateMulticastServer(char *pAddress, int port)
94 {
95    struct ip_mreq group;
96    struct sockaddr_in localSock;
97    int sd=-1, reuse = 1;
98    
99    if( (sd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
100       return -1;
101 
102    if(setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse, sizeof(reuse)) < 0)
103    {
104       close(sd);
105       return -1;
106    }
107    
108    memset((char *) &localSock, 0, sizeof(localSock));
109    localSock.sin_family = AF_INET;
110    localSock.sin_port = htons(port);
111    localSock.sin_addr.s_addr = INADDR_ANY;
112    
113    int opt = 1;
114    if(setsockopt(sd, IPPROTO_IP, IP_PKTINFO, &opt, sizeof(opt)) < 0)
115       printf("set IP_PKTINFO error\n");
116       
117    group.imr_multiaddr.s_addr = inet_addr(pAddress);
118    group.imr_interface.s_addr = inet_addr(gpLocalAddr[0]);
119    if(setsockopt(sd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *)&group, sizeof(group)) < 0)
120    {
121       perror("setsockopt IP_ADD_MEMBERSHIP error");
122       close(sd);
123       return -1;
124    }
125 
126    if(bind(sd, (struct sockaddr*)&localSock, sizeof(localSock)))
127    {
128       perror("Binding datagram socket error");
129       close(sd);
130       return -1;
131    }
132    return sd;
133 }
134 
135 char * initMyIpString(void)
136 {
137    struct ifaddrs * ifAddrStruct=NULL;
138    struct ifaddrs * ifa=NULL;
139    void * tmpAddrPtr=NULL;
140    
141    getifaddrs(&ifAddrStruct);
142    for (ifa = ifAddrStruct; ifa != NULL; ifa = ifa->ifa_next) 
143    {
144       if (ifa ->ifa_addr->sa_family==AF_INET) 
145       {   
146          char addressBuffer[INET_ADDRSTRLEN];
147          tmpAddrPtr=&((struct sockaddr_in *)ifa->ifa_addr)->sin_addr;
148          inet_ntop(AF_INET, tmpAddrPtr, addressBuffer, INET_ADDRSTRLEN);
149          // Note: you may have diffent interface name. For example:eth0, eth1
150          if(strncmp(ifa->ifa_name, "eth0", 4)==0)
151          {
152             memcpy(gpLocalAddr[0], addressBuffer, strlen(addressBuffer));
153             break;
154          } 
155       }   
156    }
157    
158    if (ifAddrStruct!=NULL) freeifaddrs(ifAddrStruct);
159    return gpLocalAddr[0];
160 }

其執行結果如下

若有需要一個現成的測試範例,可以參考 https://github.com/alb423/wsdiscovery

參考資料
  1. how-determine-origin-incoming-message-uincast-multicast-broadcast 
  2. get-destination-address-of-a-received-udp-packet 
  3. http://www.cnblogs.com/kissazi2/p/3158603.html
  4. http://man7.org/linux/man-pages/man7/ip.7.html