1 /* MiniDLNA media server
2 * This file is part of MiniDLNA.
4 * The code herein is based on the MiniUPnP Project.
5 * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/
7 * Copyright (c) 2006, Thomas Bernard
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions are met:
12 * * Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 * * Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in the
16 * documentation and/or other materials provided with the distribution.
17 * * The name of the author may not be used to endorse or promote products
18 * derived from this software without specific prior written permission.
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
24 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30 * POSSIBILITY OF SUCH DAMAGE.
39 #include <sys/socket.h>
41 #include <netinet/in.h>
42 #include <arpa/inet.h>
45 #include "minidlnapath.h"
47 #include "upnpglobalvars.h"
48 #include "upnpreplyparse.h"
49 #include "getifaddr.h"
51 #include "codelength.h"
56 #define SSDP_PORT (1900)
57 #define SSDP_MCAST_ADDR ("239.255.255.250")
60 AddMulticastMembership(int s
, struct lan_addr_s
*iface
)
63 #ifdef HAVE_STRUCT_IP_MREQN
64 struct ip_mreqn imr
; /* Ip multicast membership */
65 /* setting up imr structure */
66 memset(&imr
, '\0', sizeof(imr
));
67 imr
.imr_multiaddr
.s_addr
= inet_addr(SSDP_MCAST_ADDR
);
68 imr
.imr_ifindex
= iface
->ifindex
;
70 struct ip_mreq imr
; /* Ip multicast membership */
71 /* setting up imr structure */
72 memset(&imr
, '\0', sizeof(imr
));
73 imr
.imr_multiaddr
.s_addr
= inet_addr(SSDP_MCAST_ADDR
);
74 imr
.imr_interface
.s_addr
= iface
->addr
.s_addr
;
76 ret
= setsockopt(s
, IPPROTO_IP
, IP_ADD_MEMBERSHIP
, (void *)&imr
, sizeof(imr
));
77 if (ret
< 0 && errno
!= EADDRINUSE
)
79 DPRINTF(E_ERROR
, L_SSDP
, "setsockopt(udp, IP_ADD_MEMBERSHIP): %s\n",
87 /* Open and configure the socket listening for
88 * SSDP udp packets sent on 239.255.255.250 port 1900 */
90 OpenAndConfSSDPReceiveSocket(void)
94 struct sockaddr_in sockname
;
96 s
= socket(PF_INET
, SOCK_DGRAM
, 0);
99 DPRINTF(E_ERROR
, L_SSDP
, "socket(udp): %s\n", strerror(errno
));
103 if (setsockopt(s
, SOL_SOCKET
, SO_REUSEADDR
, &i
, sizeof(i
)) < 0)
104 DPRINTF(E_WARN
, L_SSDP
, "setsockopt(udp, SO_REUSEADDR): %s\n", strerror(errno
));
106 memset(&sockname
, 0, sizeof(struct sockaddr_in
));
107 sockname
.sin_family
= AF_INET
;
108 sockname
.sin_port
= htons(SSDP_PORT
);
109 /* NOTE : it seems it doesnt work when binding on the specific address */
110 sockname
.sin_addr
.s_addr
= htonl(INADDR_ANY
);
112 if (bind(s
, (struct sockaddr
*)&sockname
, sizeof(struct sockaddr_in
)) < 0)
114 DPRINTF(E_ERROR
, L_SSDP
, "bind(udp): %s\n", strerror(errno
));
122 /* open the UDP socket used to send SSDP notifications to
123 * the multicast group reserved for them */
125 OpenAndConfSSDPNotifySocket(struct lan_addr_s
*iface
)
128 unsigned char loopchar
= 0;
131 struct in_addr mc_if
;
132 struct sockaddr_in sockname
;
134 s
= socket(PF_INET
, SOCK_DGRAM
, 0);
137 DPRINTF(E_ERROR
, L_SSDP
, "socket(udp_notify): %s\n", strerror(errno
));
141 mc_if
.s_addr
= iface
->addr
.s_addr
;
143 if (setsockopt(s
, IPPROTO_IP
, IP_MULTICAST_LOOP
, (char *)&loopchar
, sizeof(loopchar
)) < 0)
145 DPRINTF(E_ERROR
, L_SSDP
, "setsockopt(udp_notify, IP_MULTICAST_LOOP): %s\n", strerror(errno
));
150 if (setsockopt(s
, IPPROTO_IP
, IP_MULTICAST_IF
, (char *)&mc_if
, sizeof(mc_if
)) < 0)
152 DPRINTF(E_ERROR
, L_SSDP
, "setsockopt(udp_notify, IP_MULTICAST_IF): %s\n", strerror(errno
));
157 setsockopt(s
, IPPROTO_IP
, IP_MULTICAST_TTL
, &ttl
, sizeof(ttl
));
159 if (setsockopt(s
, SOL_SOCKET
, SO_BROADCAST
, &bcast
, sizeof(bcast
)) < 0)
161 DPRINTF(E_ERROR
, L_SSDP
, "setsockopt(udp_notify, SO_BROADCAST): %s\n", strerror(errno
));
166 memset(&sockname
, 0, sizeof(struct sockaddr_in
));
167 sockname
.sin_family
= AF_INET
;
168 sockname
.sin_addr
.s_addr
= iface
->addr
.s_addr
;
170 if (bind(s
, (struct sockaddr
*)&sockname
, sizeof(struct sockaddr_in
)) < 0)
172 DPRINTF(E_ERROR
, L_SSDP
, "bind(udp_notify): %s\n", strerror(errno
));
177 if (AddMulticastMembership(sssdp
, iface
) < 0)
179 DPRINTF(E_WARN
, L_SSDP
, "Failed to add multicast membership for address %s\n",
186 static const char * const known_service_types
[] =
190 "urn:schemas-upnp-org:device:MediaServer:",
191 "urn:schemas-upnp-org:service:ContentDirectory:",
192 "urn:schemas-upnp-org:service:ConnectionManager:",
193 "urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:",
200 struct timespec sleep_time
;
202 sleep_time
.tv_sec
= 0;
203 sleep_time
.tv_nsec
= usecs
* 1000;
204 nanosleep(&sleep_time
, NULL
);
207 /* not really an SSDP "announce" as it is the response
208 * to a SSDP "M-SEARCH" */
210 SendSSDPResponse(int s
, struct sockaddr_in sockname
, int st_no
,
211 const char *host
, unsigned short port
)
216 time_t tm
= time(NULL
);
219 * follow guideline from document "UPnP Device Architecture 1.0"
220 * uppercase is recommended.
221 * DATE: is recommended
222 * SERVER: OS/ver UPnP/1.0 minidlna/1.0
223 * - check what to put in the 'Cache-Control' header
225 strftime(tmstr
, sizeof(tmstr
), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&tm
));
226 l
= snprintf(buf
, sizeof(buf
), "HTTP/1.1 200 OK\r\n"
227 "CACHE-CONTROL: max-age=%u\r\n"
232 "SERVER: " MINIDLNA_SERVER_STRING
"\r\n"
233 "LOCATION: http://%s:%u" ROOTDESC_PATH
"\r\n"
234 "Content-Length: 0\r\n"
236 (runtime_vars
.notify_interval
<<1)+10,
238 known_service_types
[st_no
],
239 (st_no
> 1 ? "1" : ""),
241 (st_no
> 0 ? "::" : ""),
242 (st_no
> 0 ? known_service_types
[st_no
] : ""),
243 (st_no
> 1 ? "1" : ""),
244 host
, (unsigned int)port
);
245 DPRINTF(E_DEBUG
, L_SSDP
, "Sending M-SEARCH response to %s:%d ST: %s\n",
246 inet_ntoa(sockname
.sin_addr
), ntohs(sockname
.sin_port
),
247 known_service_types
[st_no
]);
248 n
= sendto(s
, buf
, l
, 0,
249 (struct sockaddr
*)&sockname
, sizeof(struct sockaddr_in
) );
251 DPRINTF(E_ERROR
, L_SSDP
, "sendto(udp): %s\n", strerror(errno
));
255 SendSSDPNotifies(int s
, const char *host
, unsigned short port
,
256 unsigned int interval
)
258 struct sockaddr_in sockname
;
260 unsigned int lifetime
;
263 memset(&sockname
, 0, sizeof(struct sockaddr_in
));
264 sockname
.sin_family
= AF_INET
;
265 sockname
.sin_port
= htons(SSDP_PORT
);
266 sockname
.sin_addr
.s_addr
= inet_addr(SSDP_MCAST_ADDR
);
267 lifetime
= (interval
<< 1) + 10;
269 for (dup
= 0; dup
< 2; dup
++)
274 while (known_service_types
[i
])
276 l
= snprintf(bufr
, sizeof(bufr
),
277 "NOTIFY * HTTP/1.1\r\n"
279 "CACHE-CONTROL:max-age=%u\r\n"
280 "LOCATION:http://%s:%d" ROOTDESC_PATH
"\r\n"
281 "SERVER: " MINIDLNA_SERVER_STRING
"\r\n"
286 SSDP_MCAST_ADDR
, SSDP_PORT
,
289 known_service_types
[i
],
293 (i
> 0 ? known_service_types
[i
] : ""),
295 if (l
>= sizeof(bufr
))
297 DPRINTF(E_WARN
, L_SSDP
, "SendSSDPNotifies(): truncated output\n");
300 DPRINTF(E_MAXDEBUG
, L_SSDP
, "Sending ssdp:alive [%d]\n", s
);
301 n
= sendto(s
, bufr
, l
, 0,
302 (struct sockaddr
*)&sockname
, sizeof(struct sockaddr_in
));
304 DPRINTF(E_ERROR
, L_SSDP
, "sendto(udp_notify=%d, %s): %s\n", s
, host
, strerror(errno
));
311 ParseUPnPClient(char *location
)
314 struct sockaddr_in dest
;
315 int s
, n
, do_headers
= 0, nread
= 0;
317 char *addr
, *path
, *port_str
;
319 char *off
= NULL
, *p
;
320 int content_len
= sizeof(buf
);
321 struct NameValueParserData xml
;
322 struct client_cache_s
*client
;
324 char *model
, *serial
, *name
;
326 if (strncmp(location
, "http://", 7) != 0)
329 port_str
= strsep(&path
, "/");
332 addr
= strsep(&port_str
, ":");
335 port
= strtol(port_str
, NULL
, 10);
340 memset(&dest
, '\0', sizeof(dest
));
341 if (!inet_aton(addr
, &dest
.sin_addr
))
343 /* Check if the client is already in cache */
344 dest
.sin_family
= AF_INET
;
345 dest
.sin_port
= htons(port
);
347 s
= socket(PF_INET
, SOCK_STREAM
, 0);
353 setsockopt(s
, SOL_SOCKET
, SO_RCVTIMEO
, &tv
, sizeof(tv
));
354 setsockopt(s
, SOL_SOCKET
, SO_SNDTIMEO
, &tv
, sizeof(tv
));
356 if (connect(s
, (struct sockaddr
*)&dest
, sizeof(struct sockaddr_in
)) < 0)
359 n
= snprintf(buf
, sizeof(buf
), "GET /%s HTTP/1.0\r\n"
360 "HOST: %s:%ld\r\n\r\n",
362 if (write(s
, buf
, n
) < 1)
365 while ((n
= read(s
, buf
+nread
, sizeof(buf
)-nread
-1)) > 0)
372 while (!off
&& (n
-- > 0))
374 if (p
[0] == '\r' && p
[1] == '\n' && p
[2] == '\r' && p
[3] == '\n')
387 if (strncmp(p
, "HTTP/", 5) != 0)
389 while (*p
!= ' ' && *p
!= '\t')
391 /* If we don't get a 200 status, ignore it */
392 if (strtol(p
, NULL
, 10) != 200)
394 p
= strcasestr(p
, "Content-Length:");
396 content_len
= strtol(p
+15, NULL
, 10);
399 if ((buf
+ nread
- off
) >= content_len
)
407 ParseNameValue(off
, nread
, &xml
, 0);
408 model
= GetValueFromNameValueList(&xml
, "modelName");
409 serial
= GetValueFromNameValueList(&xml
, "serialNumber");
410 name
= GetValueFromNameValueList(&xml
, "friendlyName");
414 DPRINTF(E_DEBUG
, L_SSDP
, "Model: %s\n", model
);
415 for (i
= 0; client_types
[i
].name
; i
++)
417 if (client_types
[i
].match_type
!= EModelName
)
419 if (strstr(model
, client_types
[i
].match
) != NULL
)
426 /* Special Samsung handling. It's very hard to tell Series A from B */
427 if (type
> 0 && client_types
[type
].type
== ESamsungSeriesB
)
431 DPRINTF(E_DEBUG
, L_SSDP
, "Serial: %s\n", serial
);
432 /* The Series B I saw was 20081224DMR. Series A should be older than that. */
433 if (atoi(serial
) < 20081201)
442 if (type
== 0 && name
!= NULL
)
444 for (i
= 0; client_types
[i
].name
; i
++)
446 if (client_types
[i
].match_type
!= EFriendlyNameSSDP
)
448 if (strcmp(name
, client_types
[i
].match
) == 0)
456 ClearNameValueList(&xml
);
459 /* Add this client to the cache if it's not there already. */
460 client
= SearchClientCache(dest
.sin_addr
, 1);
463 AddClientCache(dest
.sin_addr
, type
);
467 client
->type
= &client_types
[type
];
468 client
->age
= time(NULL
);
472 /* ProcessSSDPRequest()
473 * process SSDP M-SEARCH requests and responds to them */
475 ProcessSSDPRequest(int s
, unsigned short port
)
480 struct sockaddr_in sendername
;
482 char *st
= NULL
, *mx
= NULL
, *man
= NULL
, *mx_end
= NULL
;
484 len_r
= sizeof(struct sockaddr_in
);
486 n
= recvfrom(s
, bufr
, sizeof(bufr
)-1, 0,
487 (struct sockaddr
*)&sendername
, &len_r
);
490 DPRINTF(E_ERROR
, L_SSDP
, "recvfrom(udp): %s\n", strerror(errno
));
496 if (memcmp(bufr
, "NOTIFY", 6) == 0)
498 char *loc
= NULL
, *srv
= NULL
, *nts
= NULL
, *nt
= NULL
;
500 //DEBUG DPRINTF(E_DEBUG, L_SSDP, "Received SSDP notify:\n%.*s", n, bufr);
501 for (i
= 0; i
< n
; i
++)
506 if (strcasestrc(bufr
+i
, "HTTP/1.1", '\r') == NULL
)
510 while ((i
< n
) && (bufr
[i
] != '\r' || bufr
[i
+1] != '\n'))
513 if (strncasecmp(bufr
+i
, "SERVER:", 7) == 0)
516 while (*srv
== ' ' || *srv
== '\t')
519 else if (strncasecmp(bufr
+i
, "LOCATION:", 9) == 0)
522 while (*loc
== ' ' || *loc
== '\t')
524 while (loc
[loc_len
]!='\r' && loc
[loc_len
]!='\n')
527 else if (strncasecmp(bufr
+i
, "NTS:", 4) == 0)
530 while (*nts
== ' ' || *nts
== '\t')
533 else if (strncasecmp(bufr
+i
, "NT:", 3) == 0)
536 while(*nt
== ' ' || *nt
== '\t')
540 if (!loc
|| !srv
|| !nt
|| !nts
|| (strncmp(nts
, "ssdp:alive", 10) != 0) ||
541 (strncmp(nt
, "urn:schemas-upnp-org:device:MediaRenderer", 41) != 0))
544 if ((strncmp(srv
, "Allegro-Software-RomPlug", 24) == 0) || /* Roku */
545 (strstr(loc
, "SamsungMRDesc.xml") != NULL
) || /* Samsung TV */
546 (strstrc(srv
, "DigiOn DiXiM", '\r') != NULL
)) /* Marantz Receiver */
548 /* Check if the client is already in cache */
549 struct client_cache_s
*client
= SearchClientCache(sendername
.sin_addr
, 1);
552 if (client
->type
->type
< EStandardDLNA150
&&
553 client
->type
->type
!= ESamsungSeriesA
)
555 client
->age
= time(NULL
);
559 ParseUPnPClient(loc
);
562 else if (memcmp(bufr
, "M-SEARCH", 8) == 0)
564 int st_len
= 0, mx_len
= 0, mx_val
= 0;
565 //DPRINTF(E_DEBUG, L_SSDP, "Received SSDP request:\n%.*s\n", n, bufr);
566 for (i
= 0; i
< n
; i
++)
571 if (strcasestrc(bufr
+i
, "HTTP/1.1", '\r') == NULL
)
575 while ((i
< n
) && (bufr
[i
] != '\r' || bufr
[i
+1] != '\n'))
578 if (strncasecmp(bufr
+i
, "ST:", 3) == 0)
582 while (*st
== ' ' || *st
== '\t')
584 while (st
[st_len
]!='\r' && st
[st_len
]!='\n')
587 else if (strncasecmp(bufr
+i
, "MX:", 3) == 0)
591 while (*mx
== ' ' || *mx
== '\t')
593 while (mx
[mx_len
]!='\r' && mx
[mx_len
]!='\n')
595 mx_val
= strtol(mx
, &mx_end
, 10);
597 else if (strncasecmp(bufr
+i
, "MAN:", 4) == 0)
601 while (*man
== ' ' || *man
== '\t')
603 while (man
[man_len
]!='\r' && man
[man_len
]!='\n')
607 /*DPRINTF(E_INFO, L_SSDP, "SSDP M-SEARCH packet received from %s:%d\n",
608 inet_ntoa(sendername.sin_addr), ntohs(sendername.sin_port) );*/
609 if (GETFLAG(DLNA_STRICT_MASK
) && (ntohs(sendername
.sin_port
) <= 1024 || ntohs(sendername
.sin_port
) == 1900))
611 DPRINTF(E_INFO
, L_SSDP
, "WARNING: Ignoring invalid SSDP M-SEARCH from %s [bad source port %d]\n",
612 inet_ntoa(sendername
.sin_addr
), ntohs(sendername
.sin_port
));
614 else if (!man
|| (strncmp(man
, "\"ssdp:discover\"", 15) != 0))
616 DPRINTF(E_INFO
, L_SSDP
, "WARNING: Ignoring invalid SSDP M-SEARCH from %s [bad %s header '%.*s']\n",
617 inet_ntoa(sendername
.sin_addr
), "MAN", man_len
, man
);
619 else if (!mx
|| mx
== mx_end
|| mx_val
< 0)
621 DPRINTF(E_INFO
, L_SSDP
, "WARNING: Ignoring invalid SSDP M-SEARCH from %s [bad %s header '%.*s']\n",
622 inet_ntoa(sendername
.sin_addr
), "MX", mx_len
, mx
);
624 else if (st
&& (st_len
> 0))
628 /* find in which sub network the client is */
629 for (i
= 0; i
< n_lan_addr
; i
++)
631 if((sendername
.sin_addr
.s_addr
& lan_addr
[i
].mask
.s_addr
) ==
632 (lan_addr
[i
].addr
.s_addr
& lan_addr
[i
].mask
.s_addr
))
640 DPRINTF(E_DEBUG
, L_SSDP
, "Ignoring SSDP M-SEARCH on other interface [%s]\n",
641 inet_ntoa(sendername
.sin_addr
));
644 DPRINTF(E_DEBUG
, L_SSDP
, "SSDP M-SEARCH from %s:%d ST: %.*s, MX: %.*s, MAN: %.*s\n",
645 inet_ntoa(sendername
.sin_addr
),
646 ntohs(sendername
.sin_port
),
647 st_len
, st
, mx_len
, mx
, man_len
, man
);
648 /* Responds to request with a device as ST header */
649 for (i
= 0; known_service_types
[i
]; i
++)
651 l
= strlen(known_service_types
[i
]);
652 if ((l
> st_len
) || (memcmp(st
, known_service_types
[i
], l
) != 0))
656 /* Check version number - we only support 1. */
657 if ((st
[l
-1] == ':') && (st
[l
] == '1'))
668 DPRINTF(E_MAXDEBUG
, L_SSDP
,
669 "Ignoring SSDP M-SEARCH with bad extra data '%c' [%s]\n",
670 st
[l
], inet_ntoa(sendername
.sin_addr
));
676 _usleep(random()>>20);
677 SendSSDPResponse(s
, sendername
, i
,
678 lan_addr
[iface
].str
, port
);
681 /* Responds to request with ST: ssdp:all */
682 /* strlen("ssdp:all") == 8 */
683 if ((st_len
== 8) && (memcmp(st
, "ssdp:all", 8) == 0))
685 for (i
=0; known_service_types
[i
]; i
++)
687 l
= strlen(known_service_types
[i
]);
688 SendSSDPResponse(s
, sendername
, i
,
689 lan_addr
[iface
].str
, port
);
695 DPRINTF(E_INFO
, L_SSDP
, "Invalid SSDP M-SEARCH from %s:%d\n",
696 inet_ntoa(sendername
.sin_addr
), ntohs(sendername
.sin_port
));
699 else if (memcmp(bufr
, "YOUKU-NOTIFY", 12) == 0)
705 DPRINTF(E_WARN
, L_SSDP
, "Unknown udp packet received from %s:%d\n",
706 inet_ntoa(sendername
.sin_addr
), ntohs(sendername
.sin_port
));
710 /* This will broadcast ssdp:byebye notifications to inform
711 * the network that UPnP is going down. */
713 SendSSDPGoodbyes(int s
)
715 struct sockaddr_in sockname
;
721 memset(&sockname
, 0, sizeof(struct sockaddr_in
));
722 sockname
.sin_family
= AF_INET
;
723 sockname
.sin_port
= htons(SSDP_PORT
);
724 sockname
.sin_addr
.s_addr
= inet_addr(SSDP_MCAST_ADDR
);
726 for (dup
= 0; dup
< 2; dup
++)
728 for (i
= 0; known_service_types
[i
]; i
++)
730 l
= snprintf(bufr
, sizeof(bufr
),
731 "NOTIFY * HTTP/1.1\r\n"
735 "NTS:ssdp:byebye\r\n"
737 SSDP_MCAST_ADDR
, SSDP_PORT
,
738 known_service_types
[i
],
739 (i
> 1 ? "1" : ""), uuidvalue
,
741 (i
> 0 ? known_service_types
[i
] : ""),
743 DPRINTF(E_MAXDEBUG
, L_SSDP
, "Sending ssdp:byebye [%d]\n", s
);
744 n
= sendto(s
, bufr
, l
, 0,
745 (struct sockaddr
*)&sockname
, sizeof(struct sockaddr_in
) );
748 DPRINTF(E_ERROR
, L_SSDP
, "sendto(udp_shutdown=%d): %s\n", s
, strerror(errno
));
757 /* SubmitServicesToMiniSSDPD() :
758 * register services offered by MiniUPnPd to a running instance of
761 SubmitServicesToMiniSSDPD(const char *host
, unsigned short port
)
763 struct sockaddr_un addr
;
765 unsigned char buffer
[2048];
770 s
= socket(AF_UNIX
, SOCK_STREAM
, 0);
773 DPRINTF(E_ERROR
, L_SSDP
, "socket(unix): %s", strerror(errno
));
776 addr
.sun_family
= AF_UNIX
;
777 strncpyt(addr
.sun_path
, minissdpdsocketpath
, sizeof(addr
.sun_path
));
778 if (connect(s
, (struct sockaddr
*)&addr
, sizeof(struct sockaddr_un
)) < 0)
780 DPRINTF(E_ERROR
, L_SSDP
, "connect(\"%s\"): %s",
781 minissdpdsocketpath
, strerror(errno
));
785 for (i
= 0; known_service_types
[i
]; i
++)
789 l
= strlen(known_service_types
[i
]);
793 memcpy(p
, known_service_types
[i
], l
);
797 l
= snprintf(strbuf
, sizeof(strbuf
), "%s::%s%s",
798 uuidvalue
, known_service_types
[i
], (i
==0)?"":"1");
800 memcpy(p
, strbuf
, l
);
802 l
= strlen(MINIDLNA_SERVER_STRING
);
804 memcpy(p
, MINIDLNA_SERVER_STRING
, l
);
806 l
= snprintf(strbuf
, sizeof(strbuf
), "http://%s:%u" ROOTDESC_PATH
,
807 host
, (unsigned int)port
);
809 memcpy(p
, strbuf
, l
);
811 if(write(s
, buffer
, p
- buffer
) < 0)
813 DPRINTF(E_ERROR
, L_SSDP
, "write(): %s", strerror(errno
));