| /* |
| * Copyright (c) 2012 Netflix, Inc. |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are met: |
| * |
| * Redistributions of source code must retain the above copyright notice, this |
| * list of conditions and the following disclaimer. |
| * Redistributions in binary form must reproduce the above copyright notice, |
| * this list of conditions and the following disclaimer in the documentation |
| * and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY NETFLIX, INC. AND CONTRIBUTORS "AS IS" AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| * DISCLAIMED. IN NO EVENT SHALL NETFLIX OR CONTRIBUTORS BE LIABLE FOR ANY |
| * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
| * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include <arpa/inet.h> |
| #include <net/if.h> |
| #include <netinet/in.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/ioctl.h> |
| #include <sys/socket.h> |
| #include <unistd.h> |
| |
| #include "mongoose.h" |
| |
| // TODO: Partners should define this port |
| #define SSDP_PORT (56790) |
| |
| |
| static char gBuf[4096]; |
| |
| // TODO: Partners should get the friendlyName from the system and insert here. |
| // TODO: Partners should ensure the friendlyName is the same string returned |
| // in the ISsystem::getFriendlyName DPI function. |
| // TODO: Partners should get the UUID from the system and insert here. |
| // TODO: Partners should ensure the modelName is identifiably similar to the |
| // model name returned in the ISystem::getDeviceModel DPI function |
| static const char ddxml[] = "" |
| "<?xml version=\"1.0\"?>" |
| "<root" |
| " xmlns=\"urn:schemas-upnp-org:device-1-0\"" |
| " xmlns:r=\"urn:restful-tv-org:schemas:upnp-dd\">" |
| " <specVersion>" |
| " <major>1</major>" |
| " <minor>0</minor>" |
| " </specVersion>" |
| " <device>" |
| " <deviceType>urn:schemas-upnp-org:device:tvdevice:1</deviceType>" |
| " <friendlyName>%s</friendlyName>" |
| " <manufacturer> </manufacturer>" |
| " <modelName>%s</modelName>" |
| " <UDN>uuid:%s</UDN>" |
| " </device>" |
| "</root>"; |
| |
| // TODO: Partners should get the UUID from the system and insert here. |
| static const char ssdp_reply[] = "HTTP/1.1 200 OK\r\n" |
| "LOCATION: http://%s:%d/dd.xml\r\n" |
| "CACHE-CONTROL: max-age=1800\r\n" |
| "EXT:\r\n" |
| "BOOTID.UPNP.ORG: 1\r\n" |
| "SERVER: Linux/2.6 UPnP/1.0 quick_ssdp/1.0\r\n" |
| "ST: urn:dial-multiscreen-org:service:dial:1\r\n" |
| "USN: uuid:%s::" |
| "urn:dial-multiscreen-org:service:dial:1\r\n\r\n"; |
| |
| |
| static char ip_addr[INET_ADDRSTRLEN] = "127.0.0.1"; |
| static int dial_port = 0; |
| static int my_port = 0; |
| static char friendly_name[256]; |
| static char uuid[256]; |
| static char model_name[256]; |
| static struct mg_context *ctx; |
| |
| extern int dial_allowed(); |
| |
| static void *request_handler(enum mg_event event, |
| struct mg_connection *conn, |
| const struct mg_request_info *request_info) { |
| if (event == MG_NEW_REQUEST) { |
| if (!dial_allowed()) |
| return "done"; |
| if (!strcmp(request_info->uri, "/dd.xml") && |
| !strcmp(request_info->request_method, "GET")) { |
| mg_printf(conn, "HTTP/1.1 200 OK\r\n" |
| "Content-Type: application/xml\r\n" |
| "Application-URL: http://%s:%d/apps/\r\n" |
| "\r\n", ip_addr, dial_port); |
| mg_printf(conn, ddxml, friendly_name, model_name, uuid); |
| } else { |
| mg_send_http_error(conn, 404, "Not Found", "Not Found"); |
| } |
| return "done"; |
| } |
| return NULL; |
| } |
| |
| static void get_local_address() { |
| struct ifconf ifc; |
| char buf[4096]; |
| int s, i; |
| if (-1 == (s = socket(AF_INET, SOCK_DGRAM, 0))) { |
| perror("socket"); |
| exit(1); |
| } |
| ifc.ifc_len = sizeof(buf); |
| ifc.ifc_buf = buf; |
| if (0 > ioctl(s, SIOCGIFCONF, &ifc)) { |
| perror("SIOCGIFCONF"); |
| exit(1); |
| } |
| if (ifc.ifc_len == sizeof(buf)) { |
| fprintf(stderr, "SIOCGIFCONF output too long"); |
| exit(1); |
| } |
| close(s); |
| for (i = 0; i < ifc.ifc_len/sizeof(ifc.ifc_req[0]); i++) { |
| strcpy(ip_addr, |
| inet_ntoa(((struct sockaddr_in *)(&ifc.ifc_req[i].ifr_addr))->sin_addr)); |
| // exit if we found a non-loopback address |
| if (strcmp("127.0.0.1", ip_addr)) { |
| break; |
| } |
| } |
| } |
| |
| static void handle_mcast() { |
| int s, one = 1, bytes; |
| socklen_t addrlen; |
| struct sockaddr_in saddr; |
| struct ip_mreq mreq; |
| char send_buf[sizeof(ssdp_reply) + INET_ADDRSTRLEN + 256 + 256] = {0,}; |
| int send_size; |
| |
| send_size = snprintf(send_buf, sizeof(send_buf), ssdp_reply, ip_addr, my_port, uuid); |
| |
| if (-1 == (s = socket(AF_INET, SOCK_DGRAM, 0))) { |
| perror("socket"); |
| exit(1); |
| } |
| if (-1 == setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one))) { |
| perror("reuseaddr"); |
| exit(1); |
| } |
| saddr.sin_family = AF_INET; |
| saddr.sin_addr.s_addr = inet_addr("239.255.255.250"); |
| saddr.sin_port = htons(1900); |
| if (-1 == bind(s, (struct sockaddr *)&saddr, sizeof(saddr))) { |
| perror("bind"); |
| exit(1); |
| } |
| mreq.imr_multiaddr.s_addr = inet_addr("239.255.255.250"); |
| mreq.imr_interface.s_addr = inet_addr(ip_addr); |
| if (-1 == setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, |
| &mreq, sizeof(mreq))) { |
| perror("add_membership"); |
| exit(1); |
| } |
| //printf("Starting Multicast handling on 239.255.255.250\n"); |
| while (1) { |
| addrlen = sizeof(saddr); |
| if (-1 == (bytes = recvfrom(s, gBuf, sizeof(gBuf) - 1, 0, |
| (struct sockaddr *)&saddr, &addrlen))) { |
| perror("recvfrom"); |
| continue; |
| } |
| gBuf[bytes] = 0; |
| |
| // sophisticated SSDP parsing algorithm |
| if (!strstr(gBuf, "urn:dial-multiscreen-org:service:dial:1") && |
| !strstr(gBuf, "ST: urn:dial-multiscreen-org:device:dial:1")) |
| { |
| #if 0 // use for debugging |
| fprintf(stderr, "Dropping: \n"); |
| { |
| int i; |
| for (i = 0; i < bytes; i++) |
| { |
| putchar(gBuf[i]); |
| } |
| } |
| fprintf(stderr, "\n##### End of DROP #######\n"); |
| #endif |
| continue; |
| } |
| if (dial_allowed()) { |
| fprintf(stderr, "Sending SSDP reply to %s:%d\n", |
| inet_ntoa(saddr.sin_addr), ntohs(saddr.sin_port)); |
| if (-1 == sendto(s, send_buf, send_size, 0, (struct sockaddr *)&saddr, addrlen)) { |
| perror("sendto"); |
| continue; |
| } |
| } |
| } |
| } |
| |
| void run_ssdp(int port, const char *pFriendlyName, const char * pModelName, const char *pUuid, const char **ppIpAddress) { |
| struct sockaddr sa; |
| socklen_t len = sizeof(sa); |
| |
| if(pFriendlyName) { |
| strncpy(friendly_name, pFriendlyName, sizeof(friendly_name)); |
| friendly_name[255] = '\0'; |
| } else { |
| strcpy(friendly_name, "DIAL server sample"); |
| } |
| if(pModelName) { |
| strncpy(model_name, pModelName, sizeof(model_name)); |
| uuid[255] = '\0'; |
| } else { |
| strcpy(model_name, "deadbeef-dead-beef-dead-beefdeadbeef"); |
| } |
| if(pUuid) { |
| strncpy(uuid, pUuid, sizeof(uuid)); |
| uuid[255] = '\0'; |
| } else { |
| strcpy(uuid, "deadbeef-dead-beef-dead-beefdeadbeef"); |
| } |
| |
| dial_port = port; |
| get_local_address(); |
| *ppIpAddress = ip_addr; |
| ctx = mg_start(&request_handler, NULL, SSDP_PORT); |
| |
| if (mg_get_listen_addr(ctx, &sa, &len)) { |
| my_port = ntohs(((struct sockaddr_in *)&sa)->sin_port); |
| } |
| |
| fprintf(stderr, "SSDP listening on %s:%d\n", ip_addr, my_port); |
| handle_mcast(); |
| } |