1 /* $Id: minissdp.c,v 1.27 2011/05/23 12:39:41 nanard Exp $ */
3 * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/
4 * (c) 2006-2011 Thomas Bernard
5 * This software is subject to the conditions detailed
6 * in the LICENCE file provided within the distribution */
11 #include <sys/socket.h>
13 #include <netinet/in.h>
14 #include <arpa/inet.h>
17 #include "upnpdescstrings.h"
18 #include "miniupnpdpath.h"
20 #include "upnpglobalvars.h"
22 #include "upnputils.h"
23 #include "codelength.h"
26 #define SSDP_PORT (1900)
27 #define SSDP_MCAST_ADDR ("239.255.255.250")
28 #define LL_SSDP_MCAST_ADDR ("FF02::C")
29 #define SL_SSDP_MCAST_ADDR ("FF05::C")
31 /* AddMulticastMembership()
33 * param ifaddr ip v4 address
36 AddMulticastMembership(int s
, in_addr_t ifaddr
)
38 struct ip_mreq imr
; /* Ip multicast membership */
40 /* setting up imr structure */
41 imr
.imr_multiaddr
.s_addr
= inet_addr(SSDP_MCAST_ADDR
);
42 /*imr.imr_interface.s_addr = htonl(INADDR_ANY);*/
43 imr
.imr_interface
.s_addr
= ifaddr
; /*inet_addr(ifaddr);*/
45 if (setsockopt(s
, IPPROTO_IP
, IP_ADD_MEMBERSHIP
, (void *)&imr
, sizeof(struct ip_mreq
)) < 0)
47 syslog(LOG_ERR
, "setsockopt(udp, IP_ADD_MEMBERSHIP): %m");
54 /* AddMulticastMembershipIPv6()
55 * param s socket (IPv6)
56 * To be improved to target specific network interfaces */
59 AddMulticastMembershipIPv6(int s
)
62 /*unsigned int ifindex;*/
64 memset(&mr
, 0, sizeof(mr
));
65 inet_pton(AF_INET6
, LL_SSDP_MCAST_ADDR
, &mr
.ipv6mr_multiaddr
);
66 /*mr.ipv6mr_interface = ifindex;*/
67 mr
.ipv6mr_interface
= 0; /* 0 : all interfaces */
68 #ifndef IPV6_ADD_MEMBERSHIP
69 #define IPV6_ADD_MEMBERSHIP IPV6_JOIN_GROUP
71 if(setsockopt(s
, IPPROTO_IPV6
, IPV6_ADD_MEMBERSHIP
, &mr
, sizeof(struct ipv6_mreq
)) < 0)
73 syslog(LOG_ERR
, "setsockopt(udp, IPV6_ADD_MEMBERSHIP): %m");
76 inet_pton(AF_INET6
, SL_SSDP_MCAST_ADDR
, &mr
.ipv6mr_multiaddr
);
77 if(setsockopt(s
, IPPROTO_IPV6
, IPV6_ADD_MEMBERSHIP
, &mr
, sizeof(struct ipv6_mreq
)) < 0)
79 syslog(LOG_ERR
, "setsockopt(udp, IPV6_ADD_MEMBERSHIP): %m");
86 /* Open and configure the socket listening for
87 * SSDP udp packets sent on 239.255.255.250 port 1900
88 * SSDP v6 udp packets sent on FF02::C, or FF05::C, port 1900 */
90 OpenAndConfSSDPReceiveSocket(int ipv6
)
93 struct sockaddr_storage sockname
;
94 socklen_t sockname_len
;
95 struct lan_addr_s
* lan_addr
;
98 if( (s
= socket(ipv6
? PF_INET6
: PF_INET
, SOCK_DGRAM
, 0)) < 0)
100 syslog(LOG_ERR
, "socket(udp): %m");
104 memset(&sockname
, 0, sizeof(struct sockaddr_storage
));
106 struct sockaddr_in6
* saddr
= (struct sockaddr_in6
*)&sockname
;
107 saddr
->sin6_family
= AF_INET6
;
108 saddr
->sin6_port
= htons(SSDP_PORT
);
109 saddr
->sin6_addr
= in6addr_any
;
110 sockname_len
= sizeof(struct sockaddr_in6
);
112 struct sockaddr_in
* saddr
= (struct sockaddr_in
*)&sockname
;
113 saddr
->sin_family
= AF_INET
;
114 saddr
->sin_port
= htons(SSDP_PORT
);
115 /* NOTE : it seems it doesnt work when binding on the specific address */
116 /*saddr->sin_addr.s_addr = inet_addr(UPNP_MCAST_ADDR);*/
117 saddr
->sin_addr
.s_addr
= htonl(INADDR_ANY
);
118 /*saddr->sin_addr.s_addr = inet_addr(ifaddr);*/
119 sockname_len
= sizeof(struct sockaddr_in
);
122 if(setsockopt(s
, SOL_SOCKET
, SO_REUSEADDR
, &j
, sizeof(j
)) < 0)
124 syslog(LOG_WARNING
, "setsockopt(udp, SO_REUSEADDR): %m");
128 if(bind(s
, (struct sockaddr
*)&sockname
, sockname_len
) < 0)
130 syslog(LOG_ERR
, "bind(udp%s): %m", ipv6
? "6" : "");
138 AddMulticastMembershipIPv6(s
);
143 for(lan_addr
= lan_addrs
.lh_first
; lan_addr
!= NULL
; lan_addr
= lan_addr
->list
.le_next
)
145 if(AddMulticastMembership(s
, lan_addr
->addr
.s_addr
) < 0)
148 "Failed to add multicast membership for interface %s",
157 /* open the UDP socket used to send SSDP notifications to
158 * the multicast group reserved for them */
160 OpenAndConfSSDPNotifySocket(in_addr_t addr
)
163 unsigned char loopchar
= 0;
165 struct in_addr mc_if
;
166 struct sockaddr_in sockname
;
168 if( (s
= socket(PF_INET
, SOCK_DGRAM
, 0)) < 0)
170 syslog(LOG_ERR
, "socket(udp_notify): %m");
174 mc_if
.s_addr
= addr
; /*inet_addr(addr);*/
176 if(setsockopt(s
, IPPROTO_IP
, IP_MULTICAST_LOOP
, (char *)&loopchar
, sizeof(loopchar
)) < 0)
178 syslog(LOG_ERR
, "setsockopt(udp_notify, IP_MULTICAST_LOOP): %m");
183 if(setsockopt(s
, IPPROTO_IP
, IP_MULTICAST_IF
, (char *)&mc_if
, sizeof(mc_if
)) < 0)
185 syslog(LOG_ERR
, "setsockopt(udp_notify, IP_MULTICAST_IF): %m");
190 if(setsockopt(s
, SOL_SOCKET
, SO_BROADCAST
, &bcast
, sizeof(bcast
)) < 0)
192 syslog(LOG_ERR
, "setsockopt(udp_notify, SO_BROADCAST): %m");
197 memset(&sockname
, 0, sizeof(struct sockaddr_in
));
198 sockname
.sin_family
= AF_INET
;
199 sockname
.sin_addr
.s_addr
= addr
; /*inet_addr(addr);*/
201 if (bind(s
, (struct sockaddr
*)&sockname
, sizeof(struct sockaddr_in
)) < 0)
203 syslog(LOG_ERR
, "bind(udp_notify): %m");
212 OpenAndConfSSDPNotifySockets(int * sockets
)
213 /*OpenAndConfSSDPNotifySockets(int * sockets,
214 struct lan_addr_s * lan_addr, int n_lan_addr)*/
217 struct lan_addr_s
* lan_addr
;
219 for(i
=0, lan_addr
= lan_addrs
.lh_first
; lan_addr
!= NULL
; lan_addr
= lan_addr
->list
.le_next
, i
++)
221 sockets
[i
] = OpenAndConfSSDPNotifySocket(lan_addr
->addr
.s_addr
);
236 * response from a LiveBox (Wanadoo)
238 CACHE-CONTROL: max-age=1800
239 DATE: Thu, 01 Jan 1970 04:03:23 GMT
241 LOCATION: http://192.168.0.1:49152/gatedesc.xml
242 SERVER: Linux/2.4.17, UPnP/1.0, Intel SDK for UPnP devices /1.2
244 USN: uuid:75802409-bccb-40e7-8e6c-fa095ecce13e::upnp:rootdevice
246 * response from a Linksys 802.11b :
248 Cache-Control:max-age=120
249 Location:http://192.168.5.1:5678/rootDesc.xml
250 Server:NT/5.0 UPnP/1.0
252 USN:uuid:upnp-InternetGatewayDevice-1_0-0090a2777777::upnp:rootdevice
256 /* not really an SSDP "announce" as it is the response
257 * to a SSDP "M-SEARCH" */
259 SendSSDPAnnounce2(int s
, const struct sockaddr
* addr
,
260 const char * st
, int st_len
, const char * suffix
,
261 const char * host
, unsigned short port
)
267 * follow guideline from document "UPnP Device Architecture 1.0"
268 * uppercase is recommended.
269 * DATE: is recommended
270 * SERVER: OS/ver UPnP/1.0 miniupnpd/1.0
271 * - check what to put in the 'Cache-Control' header
273 * have a look at the document "UPnP Device Architecture v1.1 */
274 l
= snprintf(buf
, sizeof(buf
), "HTTP/1.1 200 OK\r\n"
275 "CACHE-CONTROL: max-age=120\r\n"
278 "USN: %s::%.*s%s\r\n"
280 "SERVER: " MINIUPNPD_SERVER_STRING
"\r\n"
281 "LOCATION: http://%s:%u" ROOTDESC_PATH
"\r\n"
282 "OPT: \"http://schemas.upnp.org/upnp/1/0/\";\r\n" /* UDA v1.1 */
283 "01-NLS: %u\r\n" /* same as BOOTID. UDA v1.1 */
284 "BOOTID.UPNP.ORG: %u\r\n" /* UDA v1.1 */
285 "CONFIGID.UPNP.ORG: %u\r\n" /* UDA v1.1 */
288 uuidvalue
, st_len
, st
, suffix
,
289 host
, (unsigned int)port
,
290 upnp_bootid
, upnp_bootid
, upnp_configid
);
291 addrlen
= (addr
->sa_family
== AF_INET6
)
292 ? sizeof(struct sockaddr_in6
) : sizeof(struct sockaddr_in
);
293 n
= sendto(s
, buf
, l
, 0,
295 syslog(LOG_INFO
, "SSDP Announce %d bytes to %s:%d ST: %.*s",n
,
296 inet_ntoa(((const struct sockaddr_in
*)addr
)->sin_addr
),
297 ntohs(((const struct sockaddr_in
*)addr
)->sin_port
),
301 syslog(LOG_ERR
, "sendto(udp): %m");
305 static const char * const known_service_types
[] =
308 "urn:schemas-upnp-org:device:InternetGatewayDevice:",
309 "urn:schemas-upnp-org:device:WANConnectionDevice:",
310 "urn:schemas-upnp-org:device:WANDevice:",
311 "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:",
312 "urn:schemas-upnp-org:service:WANIPConnection:",
313 "urn:schemas-upnp-org:service:WANPPPConnection:",
314 #ifdef ENABLE_L3F_SERVICE
315 "urn:schemas-upnp-org:service:Layer3Forwarding:",
317 #ifdef ENABLE_6FC_SERVICE
318 "url:schemas-upnp-org:service:WANIPv6FirewallControl:",
324 SendSSDPNotifies(int s
, const char * host
, unsigned short port
,
325 unsigned int lifetime
)
327 struct sockaddr_in sockname
;
331 memset(&sockname
, 0, sizeof(struct sockaddr_in
));
332 sockname
.sin_family
= AF_INET
;
333 sockname
.sin_port
= htons(SSDP_PORT
);
334 sockname
.sin_addr
.s_addr
= inet_addr(SSDP_MCAST_ADDR
);
336 while(known_service_types
[i
])
338 l
= snprintf(bufr
, sizeof(bufr
),
339 "NOTIFY * HTTP/1.1\r\n"
341 "CACHE-CONTROL: max-age=%u\r\n"
342 "lOCATION: http://%s:%d" ROOTDESC_PATH
"\r\n"
343 "SERVER: " MINIUPNPD_SERVER_STRING
"\r\n"
346 "NTS: ssdp:alive\r\n"
347 "OPT: \"http://schemas.upnp.org/upnp/1/0/\";\r\n" /* UDA v1.1 */
348 "01-NLS: %u\r\n" /* same as BOOTID field. UDA v1.1 */
349 "BOOTID.UPNP.ORG: %u\r\n" /* UDA v1.1 */
350 "CONFIGID.UPNP.ORG: %u\r\n" /* UDA v1.1 */
352 SSDP_MCAST_ADDR
, SSDP_PORT
,
355 known_service_types
[i
], (i
==0?"":"1"),
356 uuidvalue
, known_service_types
[i
], (i
==0?"":"1"),
357 upnp_bootid
, upnp_bootid
, upnp_configid
);
360 syslog(LOG_WARNING
, "SendSSDPNotifies(): truncated output");
363 n
= sendto(s
, bufr
, l
, 0,
364 (struct sockaddr
*)&sockname
, sizeof(struct sockaddr_in
) );
367 syslog(LOG_ERR
, "sendto(udp_notify=%d, %s): %m", s
, host
);
374 SendSSDPNotifies2(int * sockets
,
376 unsigned int lifetime
)
379 struct lan_addr_s
* lan_addr
;
380 for(i
=0, lan_addr
= lan_addrs
.lh_first
; lan_addr
!= NULL
; lan_addr
= lan_addr
->list
.le_next
, i
++)
382 SendSSDPNotifies(sockets
[i
], lan_addr
->str
, port
, lifetime
);
386 /* ProcessSSDPRequest()
387 * process SSDP M-SEARCH requests and responds to them */
389 ProcessSSDPRequest(int s
, unsigned short port
)
395 struct sockaddr_storage sendername
;
396 len_r
= sizeof(struct sockaddr_storage
);
398 struct sockaddr_in sendername
;
399 len_r
= sizeof(struct sockaddr_in
);
402 n
= recvfrom(s
, bufr
, sizeof(bufr
), 0,
403 (struct sockaddr
*)&sendername
, &len_r
);
406 syslog(LOG_ERR
, "recvfrom(udp): %m");
409 ProcessSSDPData(s
, bufr
, n
, (struct sockaddr
*)&sendername
, port
);
414 ProcessSSDPData(int s
, const char *bufr
, int n
,
415 const struct sockaddr
* sender
, unsigned short port
) {
417 struct lan_addr_s
* lan_addr
= NULL
;
418 const char * st
= NULL
;
421 const char * announced_host
= NULL
;
423 /* get the string representation of the sender address */
424 sockaddr_to_string(sender
, sender_str
, sizeof(sender_str
));
426 if(memcmp(bufr
, "NOTIFY", 6) == 0)
428 /* ignore NOTIFY packets. We could log the sender and device type */
431 else if(memcmp(bufr
, "M-SEARCH", 8) == 0)
436 while((i
< n
- 1) && (bufr
[i
] != '\r' || bufr
[i
+1] != '\n'))
439 if((i
< n
- 3) && (strncasecmp(bufr
+i
, "st:", 3) == 0))
443 while((*st
== ' ' || *st
== '\t') && (st
< bufr
+ n
))
445 while(st
[st_len
]!='\r' && st
[st_len
]!='\n'
446 && (st
+ st_len
< bufr
+ n
))
448 /*syslog(LOG_INFO, "ST: %.*s", st_len, st);*/
450 /*while(bufr[i+j]!='\r') j++;*/
451 /*syslog(LOG_INFO, "%.*s", j, bufr+i);*/
454 /*syslog(LOG_INFO, "SSDP M-SEARCH packet received from %s",
456 if(st
&& (st_len
> 0))
458 /* TODO : doesnt answer at once but wait for a random time */
459 syslog(LOG_INFO
, "SSDP M-SEARCH from %s ST: %.*s",
460 sender_str
, st_len
, st
);
461 /* find in which sub network the client is */
462 if(sender
->sa_family
== AF_INET
)
464 for(lan_addr
= lan_addrs
.lh_first
;
466 lan_addr
= lan_addr
->list
.le_next
)
468 if( (((const struct sockaddr_in
*)sender
)->sin_addr
.s_addr
& lan_addr
->mask
.s_addr
)
469 == (lan_addr
->addr
.s_addr
& lan_addr
->mask
.s_addr
))
472 if (lan_addr
== NULL
)
474 syslog(LOG_ERR
, "Can't find in which sub network the client is");
477 announced_host
= lan_addr
->str
;
482 /* IPv6 address with brackets */
483 announced_host
= ipv6_addr_for_http_with_brackets
;
486 /* Responds to request with a device as ST header */
487 for(i
= 0; known_service_types
[i
]; i
++)
489 l
= (int)strlen(known_service_types
[i
]);
490 if(l
<=st_len
&& (0 == memcmp(st
, known_service_types
[i
], l
)))
492 syslog(LOG_INFO
, "Single search found");
493 SendSSDPAnnounce2(s
, sender
,
495 announced_host
, port
);
499 /* Responds to request with ST: ssdp:all */
500 /* strlen("ssdp:all") == 8 */
501 if(st_len
==8 && (0 == memcmp(st
, "ssdp:all", 8)))
503 syslog(LOG_INFO
, "ssdp:all found");
504 for(i
=0; known_service_types
[i
]; i
++)
506 l
= (int)strlen(known_service_types
[i
]);
507 SendSSDPAnnounce2(s
, sender
,
508 known_service_types
[i
], l
, i
==0?"":"1",
509 announced_host
, port
);
512 /* responds to request by UUID value */
513 l
= (int)strlen(uuidvalue
);
514 if(l
==st_len
&& (0 == memcmp(st
, uuidvalue
, l
)))
516 syslog(LOG_INFO
, "ssdp:uuid found");
517 SendSSDPAnnounce2(s
, sender
, st
, st_len
, "",
518 announced_host
, port
);
523 syslog(LOG_INFO
, "Invalid SSDP M-SEARCH from %s", sender_str
);
528 syslog(LOG_NOTICE
, "Unknown udp packet received from %s", sender_str
);
532 /* This will broadcast ssdp:byebye notifications to inform
533 * the network that UPnP is going down. */
535 SendSSDPGoodbye(int * sockets
, int n_sockets
)
537 struct sockaddr_in sockname
;
542 memset(&sockname
, 0, sizeof(struct sockaddr_in
));
543 sockname
.sin_family
= AF_INET
;
544 sockname
.sin_port
= htons(SSDP_PORT
);
545 sockname
.sin_addr
.s_addr
= inet_addr(SSDP_MCAST_ADDR
);
547 for(j
=0; j
<n_sockets
; j
++)
549 for(i
=0; known_service_types
[i
]; i
++)
551 l
= snprintf(bufr
, sizeof(bufr
),
552 "NOTIFY * HTTP/1.1\r\n"
556 "NTS: ssdp:byebye\r\n"
557 "OPT: \"http://schemas.upnp.org/upnp/1/0/\";\r\n" /* UDA v1.1 */
558 "01-NLS: %u\r\n" /* same as BOOTID field. UDA v1.1 */
559 "BOOTID.UPNP.ORG: %u\r\n" /* UDA v1.1 */
560 "CONFIGID.UPNP.ORG: %u\r\n" /* UDA v1.1 */
562 SSDP_MCAST_ADDR
, SSDP_PORT
,
563 known_service_types
[i
], (i
==0?"":"1"),
564 uuidvalue
, known_service_types
[i
], (i
==0?"":"1"),
565 upnp_bootid
, upnp_bootid
, upnp_configid
);
566 n
= sendto(sockets
[j
], bufr
, l
, 0,
567 (struct sockaddr
*)&sockname
, sizeof(struct sockaddr_in
) );
570 syslog(LOG_ERR
, "SendSSDPGoodbye: sendto(udp_shutdown=%d): %m",
579 /* SubmitServicesToMiniSSDPD() :
580 * register services offered by MiniUPnPd to a running instance of
583 SubmitServicesToMiniSSDPD(const char * host
, unsigned short port
) {
584 struct sockaddr_un addr
;
586 unsigned char buffer
[2048];
591 s
= socket(AF_UNIX
, SOCK_STREAM
, 0);
593 syslog(LOG_ERR
, "socket(unix): %m");
596 addr
.sun_family
= AF_UNIX
;
597 strncpy(addr
.sun_path
, minissdpdsocketpath
, sizeof(addr
.sun_path
));
598 if(connect(s
, (struct sockaddr
*)&addr
, sizeof(struct sockaddr_un
)) < 0) {
599 syslog(LOG_ERR
, "connect(\"%s\"): %m", minissdpdsocketpath
);
602 for(i
= 0; known_service_types
[i
]; i
++) {
605 l
= (int)strlen(known_service_types
[i
]);
609 memcpy(p
, known_service_types
[i
], l
);
613 l
= snprintf(strbuf
, sizeof(strbuf
), "%s::%s%s",
614 uuidvalue
, known_service_types
[i
], (i
==0)?"":"1");
616 memcpy(p
, strbuf
, l
);
618 l
= (int)strlen(MINIUPNPD_SERVER_STRING
);
620 memcpy(p
, MINIUPNPD_SERVER_STRING
, l
);
622 l
= snprintf(strbuf
, sizeof(strbuf
), "http://%s:%u" ROOTDESC_PATH
,
623 host
, (unsigned int)port
);
625 memcpy(p
, strbuf
, l
);
627 if(write(s
, buffer
, p
- buffer
) < 0) {
628 syslog(LOG_ERR
, "write(): %m");