diff --git a/ssdpd/Makefile b/ssdpd/Makefile new file mode 100644 index 000000000..9f1be6437 --- /dev/null +++ b/ssdpd/Makefile @@ -0,0 +1,58 @@ +# +# Copyright (C) 2022 OpenWrt.org +# + +include $(TOPDIR)/rules.mk + +PKG_NAME:=ssdpd +PKG_VERSION:=1.0.0 + +LOCAL_DEV:=0 +ifneq ($(LOCAL_DEV),1) +PKG_SOURCE_PROTO:=git +PKG_SOURCE_URL:=https://github.com/miniupnp/miniupnp.git +PKG_SOURCE_VERSION:=207cf440a22c075cb55fb067a850be4f9c204e6e +PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION)-$(PKG_SOURCE_VERSION).tar.gz +PKG_MIRROR_HASH:=skip +endif + +PKG_LICENSE:=BSD-3-Clause +PKG_LICENSE_FILES:=LICENSE + +include $(INCLUDE_DIR)/package.mk + +define Package/ssdpd + SECTION:=net + CATEGORY:=Network + DEPENDS:=+libnfnetlink +libpthread +libubox +libubus +libblobmsg-json +libcurl +mxml + TITLE:=MiniSSDPd - SSDP daemon + URL:=https://miniupnp.tuxfamily.org/minissdpd.html +endef + +MAKE_PATH:=minissdpd + +TARGET_CFLAGS += \ + -D_GNU_SOURCE \ + -Wall -Wextra -Werror + +TARGET_LDFLAGS += \ + -lpthread -lubox -lubus -lblobmsg_json -lcurl -lmxml + +ifeq ($(LOCAL_DEV),1) +define Build/Prepare + $(CP) -rf ~/git/sspd/* $(PKG_BUILD_DIR)/ +endef +endif + +define Package/ssdpd/install + $(INSTALL_DIR) $(1)/etc/upnp + $(INSTALL_DIR) $(1)/etc/upnp/description + $(INSTALL_DIR) $(1)/etc/config + $(INSTALL_DIR) $(1)/etc/init.d + $(INSTALL_DIR) $(1)/usr/sbin + $(INSTALL_CONF) ./files/etc/config/ssdpd $(1)/etc/config/ssdpd + $(INSTALL_BIN) ./files/etc/init.d/ssdpd $(1)/etc/init.d/ssdpd + $(INSTALL_BIN) $(PKG_BUILD_DIR)/minissdpd/minissdpd $(1)/usr/sbin/ssdpd +endef + +$(eval $(call BuildPackage,ssdpd)) diff --git a/ssdpd/files/etc/config/ssdpd b/ssdpd/files/etc/config/ssdpd new file mode 100644 index 000000000..2f786d5be --- /dev/null +++ b/ssdpd/files/etc/config/ssdpd @@ -0,0 +1,7 @@ + +config ssdpd 'ssdp' + option enabled '1' + option ipv6_enabled '0' + option socket_path '/var/run/minissdpd.sock' + option ttl '2' + option interface '' diff --git a/ssdpd/files/etc/init.d/ssdpd b/ssdpd/files/etc/init.d/ssdpd new file mode 100755 index 000000000..990557e4f --- /dev/null +++ b/ssdpd/files/etc/init.d/ssdpd @@ -0,0 +1,73 @@ +#!/bin/sh /etc/rc.common + +START=80 +STOP=02 + +USE_PROCD=1 +PROG=/usr/sbin/ssdpd + +log() { + echo "${@}"|logger -t ssdp.init -p info +} + +validate_ssdpd_ssdp_section() +{ + uci_validate_section ssdpd ssdpd "ssdp" \ + 'enabled:bool:true' \ + 'ipv6_enabled:bool:false' \ + 'socket_path:string' \ + 'ttl:uinteger' \ + 'interface:string' +} + +configure_ssdp() +{ + local enabled ipv6_enabled socket_path ttl interface + + config_load ssdpd + + validate_ssdpd_ssdp_section || { + log "Validation of ssdp section failed" + return 1; + } + + [ ${enabled} -eq 0 ] && return 0 + + if [ ${ipv6_enabled} -eq 1 ]; then + procd_append_param command -6 + fi + + if [ -n "${socket_path}" ]; then + procd_append_param command -s ${socket_path} + fi + + if [ -n "${ttl}" ]; then + procd_append_param command -t ${ttl} + fi + + if [ -z "${interface}" ]; then + iface=$(uci -q get cwmp.cpe.default_lan_interface) + interface=$(ifstatus ${iface} | jsonfilter -e @.device) + fi + + procd_append_param command -i ${interface} + procd_append_param command -d +} + +start_service() { + procd_open_instance ssdp + procd_set_param command ${PROG} + configure_ssdp + procd_set_param respawn + procd_close_instance +} + +reload_service() { + stop + start +} + +service_triggers() +{ + procd_add_reload_trigger "ssdpd" +} diff --git a/ssdpd/patches/001-fix-gcc-warnings b/ssdpd/patches/001-fix-gcc-warnings new file mode 100644 index 000000000..305fdc7e3 --- /dev/null +++ b/ssdpd/patches/001-fix-gcc-warnings @@ -0,0 +1,20 @@ +--- a/minissdpd/openssdpsocket.c ++++ b/minissdpd/openssdpsocket.c +@@ -11,6 +11,7 @@ + #include + #include + #include ++#include + #include + #include + #include +--- a/minissdpd/ifacewatch.c ++++ b/minissdpd/ifacewatch.c +@@ -130,6 +130,7 @@ ProcessInterfaceWatch(int s, int s_ssdp, + /* case RTM_DELLINK: */ + case RTM_DELADDR: + is_del = 1; ++ // fall through + case RTM_NEWADDR: + /* http://linux-hacks.blogspot.fr/2009/01/sample-code-to-learn-netlink.html */ + ifa = (struct ifaddrmsg *)NLMSG_DATA(nlhdr); diff --git a/ssdpd/patches/010-add-test-object b/ssdpd/patches/010-add-test-object new file mode 100644 index 000000000..f0afe6165 --- /dev/null +++ b/ssdpd/patches/010-add-test-object @@ -0,0 +1,11 @@ +--- a/minissdpd/Makefile ++++ b/minissdpd/Makefile +@@ -41,7 +41,7 @@ endif + EXECUTABLES = minissdpd testminissdpd testcodelength \ + showminissdpdnotif + MINISSDPDOBJS = minissdpd.o openssdpsocket.o daemonize.o upnputils.o \ +- ifacewatch.o getroute.o getifaddr.o asyncsendto.o ++ ifacewatch.o getroute.o getifaddr.o asyncsendto.o ssdpd.o + TESTMINISSDPDOBJS = testminissdpd.o printresponse.o + SHOWMINISSDPDNOTIFOBJS = showminissdpdnotif.o printresponse.o + diff --git a/ssdpd/patches/011-add-ubus-object b/ssdpd/patches/011-add-ubus-object new file mode 100644 index 000000000..77f44de6f --- /dev/null +++ b/ssdpd/patches/011-add-ubus-object @@ -0,0 +1,629 @@ +--- /dev/null ++++ b/minissdpd/ssdpd.c +@@ -0,0 +1,626 @@ ++/* ++ * Copyright (C) 2022 iopsys Software Solutions AB ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU Lesser General Public License version 2.1 ++ * as published by the Free Software Foundation ++ * ++ * Author: Amin Ben Romdhane ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "codelength.h" ++ ++struct UPNPDev { ++ struct list_head list; ++ char *descURL; ++ char *st; ++ char *usn; ++}; ++ ++struct desc_list_elt { ++ struct list_head list; ++ char *url; ++ char *desc_path; ++ bool is_device_desc; ++}; ++ ++#define UPNP_DESC_PATH "/etc/upnp/description" ++#define UPNP_DISCOVER_TIMEOUT (30 * 1000) ++ ++#ifndef MIN ++#define MIN(a, b) (((a) < (b)) ? (a) : (b)) ++#endif /* MIN */ ++ ++/* macros used to read from unix socket */ ++#define READ_BYTE_BUFFER(c) \ ++ if ((int)bufferindex >= n) { \ ++ n = read(s, buffer, sizeof(buffer)); \ ++ if (n <= 0) break; \ ++ bufferindex = 0; \ ++ } \ ++ c = buffer[bufferindex++]; ++ ++#define READ_COPY_BUFFER(dst, len) \ ++ for (l = len, p = (unsigned char *)dst; l > 0; ) { \ ++ unsigned int lcopy; \ ++ if ((int)bufferindex >= n) { \ ++ n = read(s, buffer, sizeof(buffer)); \ ++ if ( n<= 0) break; \ ++ bufferindex = 0; \ ++ } \ ++ lcopy = MIN(l, (n - bufferindex)); \ ++ memcpy(p, buffer + bufferindex, lcopy); \ ++ l -= lcopy; \ ++ p += lcopy; \ ++ bufferindex += lcopy; \ ++ } ++ ++LIST_HEAD(dev_list); ++LIST_HEAD(desc_list); ++ ++char *ssdp_sockpath = NULL; ++ ++static void upnp_discover_devices(struct uloop_timeout *timeout); ++static struct uloop_timeout upnpdiscover_timer = { .cb = upnp_discover_devices }; ++ ++static void add_dev_to_dev_list(char *descURL, char *st, char *usn) ++{ ++ struct UPNPDev *dev = NULL; ++ ++ dev = calloc(1, sizeof(struct UPNPDev)); ++ list_add_tail(&dev->list, &dev_list); ++ ++ dev->descURL = descURL; ++ dev->st = st; ++ dev->usn = usn; ++} ++ ++void free_all_dev_list(void) ++{ ++ struct UPNPDev *dev = NULL; ++ ++ while (dev_list.next != &dev_list) { ++ dev = list_entry(dev_list.next, struct UPNPDev, list); ++ free(dev->descURL); ++ free(dev->st); ++ free(dev->usn); ++ free(dev); ++ list_del(&dev->list); ++ } ++} ++ ++static int connectToMiniSSDPD(void) ++{ ++ int s = 0; ++ struct sockaddr_un addr; ++ ++ s = socket(AF_UNIX, SOCK_STREAM, 0); ++ if(s < 0) ++ return -1; ++ ++ char *ssdp_s = ssdp_sockpath ? ssdp_sockpath : "/var/run/minissdpd.sock"; ++ ++ memset(&addr, 0, sizeof(addr)); ++ addr.sun_family = AF_UNIX; ++ ++ strncpy(addr.sun_path, ssdp_s, sizeof(addr.sun_path)); ++ ++ if(connect(s, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0) { ++ close(s); ++ return -1; ++ } ++ ++ return s; ++} ++ ++static int disconnectFromMiniSSDPD(int s) ++{ ++ if (close(s) < 0) ++ return -1; ++ return 0; ++} ++ ++static int requestDevicesFromMiniSSDPD(int s) ++{ ++ unsigned char buffer[256]; ++ unsigned char *p = NULL; ++ unsigned int stsize = 0, l = 0; ++ char *devtype = "ssdp:all"; ++ ++ buffer[0] = 3; /* request type 3 : everything */ ++ stsize = strlen(devtype); ++ ++ p = buffer + 1; ++ l = stsize; CODELENGTH(l, p); ++ if (p + stsize > buffer + sizeof(buffer)) ++ return -1; ++ ++ memcpy(p, devtype, stsize); ++ p += stsize; ++ if (write(s, buffer, p - buffer) < 0) ++ return -1; ++ ++ return 0; ++} ++ ++static int receiveDevicesFromMiniSSDPD(int s) ++{ ++ unsigned char buffer[256]; ++ ssize_t n; ++ unsigned char *p; ++ unsigned int bufferindex; ++ unsigned int i, ndev; ++ unsigned int urlsize, stsize, usnsize, l; ++ char *url, *st, *usn; ++ ++ n = read(s, buffer, sizeof(buffer)); ++ if (n <= 0) ++ return -1; ++ ++ ndev = buffer[0]; ++ bufferindex = 1; ++ for (i = 0; i < ndev; i++) { ++ DECODELENGTH_READ(urlsize, READ_BYTE_BUFFER); ++ if (n <= 0) ++ return -1; ++ ++ url = (char *)malloc(urlsize); ++ if (url == NULL) ++ return -1; ++ ++ READ_COPY_BUFFER(url, urlsize); ++ if (n <= 0) ++ return -1; ++ ++ DECODELENGTH_READ(stsize, READ_BYTE_BUFFER); ++ if (n <= 0) ++ goto free_url_and_return; ++ ++ st = (char *)malloc(stsize); ++ if (st == NULL) ++ goto free_url_and_return; ++ ++ READ_COPY_BUFFER(st, stsize); ++ if (n <= 0) ++ goto free_url_and_st_and_return; ++ ++ DECODELENGTH_READ(usnsize, READ_BYTE_BUFFER); ++ if (n <= 0) ++ goto free_url_and_st_and_return; ++ ++ usn = (char *)malloc(usnsize); ++ if (usn == NULL) ++ goto free_url_and_st_and_return; ++ ++ READ_COPY_BUFFER(usn, usnsize); ++ if (n <= 0) ++ goto free_url_and_st_and_usn_and_return; ++ ++ add_dev_to_dev_list(url, st, usn); ++ } ++ ++ return 0; ++ ++free_url_and_st_and_usn_and_return: ++ free(usn); ++free_url_and_st_and_return: ++ free(st); ++free_url_and_return: ++ free(url); ++ return -1; ++} ++ ++static int getDevicesFromMiniSSDPD(void) ++{ ++ int s = 0; ++ int res = 0; ++ ++ s = connectToMiniSSDPD(); ++ if (s < 0) ++ return -1; ++ ++ res = requestDevicesFromMiniSSDPD(s); ++ if (res < 0) ++ goto close_socket_and_return; ++ ++ res = receiveDevicesFromMiniSSDPD(s); ++ ++close_socket_and_return: ++ disconnectFromMiniSSDPD(s); ++ ++ return res; ++} ++ ++static void download_file(char *file_path, const char *url) ++{ ++ CURL *curl = curl_easy_init(); ++ if (curl) { ++ curl_easy_setopt(curl, CURLOPT_URL, url); ++ curl_easy_setopt(curl, CURLOPT_TIMEOUT, 500); ++ ++ FILE *fp = fopen(file_path, "wb"); ++ if (fp) { ++ curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp); ++ curl_easy_perform(curl); ++ fclose(fp); ++ } ++ ++ curl_easy_cleanup(curl); ++ } ++} ++ ++static bool is_desc_exist(const char *desc_url) ++{ ++ struct desc_list_elt *desc_elt = NULL; ++ ++ if (!desc_url) ++ return false; ++ ++ list_for_each_entry(desc_elt, &desc_list, list) { ++ if (strcmp(desc_elt->url, desc_url) == 0) ++ return true; ++ } ++ ++ return false; ++} ++ ++static void get_desc_name(const char *desc_url, char *str, size_t len) ++{ ++ if (!desc_url || !str || len == 0) ++ return; ++ ++ char *p = strstr(desc_url, "://"); ++ ++ snprintf(str, len, "%s", p ? p + 3 : desc_url); ++ ++ for (int i = 0; str[i]; i++) { ++ if (str[i] == '/') ++ str[i] = '_'; ++ } ++} ++ ++static void add_desc_to_desc_list(const char *desc_path, const char *url, int is_device_desc) ++{ ++ struct desc_list_elt *desc_elt; ++ ++ desc_elt = calloc(1, sizeof(struct desc_list_elt)); ++ list_add_tail(&desc_elt->list, &desc_list); ++ ++ desc_elt->desc_path = strdup(desc_path); ++ desc_elt->url = strdup(url); ++ desc_elt->is_device_desc = is_device_desc; ++} ++ ++static void free_all_desc_list(void) ++{ ++ struct desc_list_elt *desc_elt = NULL; ++ ++ while (desc_list.next != &desc_list) { ++ desc_elt = list_entry(desc_list.next, struct desc_list_elt, list); ++ free(desc_elt->desc_path); ++ free(desc_elt->url); ++ free(desc_elt); ++ list_del(&desc_elt->list); ++ } ++} ++ ++static void __upnp_discover_devices(void) ++{ ++ struct UPNPDev *dev = NULL; ++ char desc_name[128] = {0}; ++ char file_path[256] = {0}; ++ int res = 0, is_device_desc = 0; ++ ++ /* ++ * Discover devices ++ */ ++ if (!list_empty(&dev_list)) ++ free_all_dev_list(); ++ ++ res = getDevicesFromMiniSSDPD(); ++ if (res) ++ goto end; ++ ++ /* ++ * Download description files ++ */ ++ list_for_each_entry_reverse(dev, &dev_list, list) { ++ ++ if (is_desc_exist(dev->descURL)) ++ continue; ++ ++ get_desc_name(dev->descURL, desc_name, sizeof(desc_name)); ++ snprintf(file_path, sizeof(file_path), "%s/%s", UPNP_DESC_PATH, desc_name); ++ is_device_desc = (dev->usn && strstr(dev->usn, ":service:")) ? 0 : 1; ++ ++ // Download Description ++ download_file(file_path, dev->descURL); ++ ++ // Add description to descriptions list ++ add_desc_to_desc_list(file_path, dev->descURL, is_device_desc); ++ } ++ ++end: ++ uloop_timeout_set(&upnpdiscover_timer, UPNP_DISCOVER_TIMEOUT); ++} ++ ++static int upnp_discovery_res(struct ubus_context *ctx, struct ubus_object *obj __attribute__((unused)), ++ struct ubus_request_data *req, const char *method __attribute__((unused)), struct blob_attr *msg __attribute__((unused))) ++{ ++ struct blob_buf bb = {0}; ++ struct UPNPDev *dev = NULL; ++ ++ memset(&bb,0,sizeof(struct blob_buf)); ++ blob_buf_init(&bb, 0); ++ ++ void *devices_array = blobmsg_open_array(&bb, "devices"); ++ list_for_each_entry_reverse(dev, &dev_list, list) { ++ // Parse Root device and devices ++ if ((dev->st && strstr(dev->st, ":rootdevice") != NULL) || (dev->usn && strstr(dev->usn, ":device:") != NULL)) { ++ void *device_obj = blobmsg_open_table(&bb, NULL); ++ blobmsg_add_string(&bb, "descurl", dev->descURL); ++ blobmsg_add_string(&bb, "st", dev->st); ++ blobmsg_add_string(&bb, "usn", dev->usn); ++ blobmsg_add_string(&bb, "is_root_device", dev->st && strstr(dev->st, ":rootdevice") ? "1" : "0"); ++ blobmsg_close_table(&bb, device_obj); ++ } ++ } ++ blobmsg_close_array(&bb, devices_array); ++ ++ void *services_array = blobmsg_open_array(&bb, "services"); ++ list_for_each_entry_reverse(dev, &dev_list, list) { ++ // Parse Services ++ if (dev->usn && strstr(dev->usn, ":service:") != NULL) { ++ void *service_obj = blobmsg_open_table(&bb, NULL); ++ blobmsg_add_string(&bb, "descurl", dev->descURL); ++ blobmsg_add_string(&bb, "st", dev->st); ++ blobmsg_add_string(&bb, "usn", dev->usn); ++ blobmsg_close_table(&bb, service_obj); ++ } ++ } ++ blobmsg_close_array(&bb, services_array); ++ ++ ubus_send_reply(ctx, req, bb.head); ++ blob_buf_free(&bb); ++ return 0; ++} ++ ++static void fill_device_instances(struct blob_buf *bb, mxml_node_t *device) ++{ ++ void *device_obj = NULL; ++ mxml_node_t *b = device; ++ char buf[64] = {0}; ++ bool new_device_discovery = false; ++ ++ while (b) { ++ ++ if (mxmlGetType(b) != MXML_ELEMENT) { ++ b = mxmlWalkNext(b, device, MXML_DESCEND); ++ continue; ++ } ++ ++ const char *elm_name = mxmlGetElement(b); ++ const char *elm_val = mxmlGetOpaque(b); ++ ++ if (elm_name && strcmp(elm_name, "device") == 0) { ++ ++ if (new_device_discovery && device_obj) ++ blobmsg_close_table(bb, device_obj); ++ ++ device_obj = blobmsg_open_table(bb, NULL); ++ blobmsg_add_string(bb, "parent_dev", buf); ++ new_device_discovery = true; ++ } ++ ++ if (elm_name && strcmp(elm_name, "deviceType") == 0 && new_device_discovery) ++ blobmsg_add_string(bb, "deviceType", elm_val ? elm_val : ""); ++ ++ if (elm_name && strcmp(elm_name, "friendlyName") == 0 && new_device_discovery) ++ blobmsg_add_string(bb, "friendlyName", elm_val ? elm_val : ""); ++ ++ if (elm_name && strcmp(elm_name, "manufacturer") == 0 && new_device_discovery) ++ blobmsg_add_string(bb, "manufacturer", elm_val ? elm_val : ""); ++ ++ if (elm_name && strcmp(elm_name, "manufacturerURL") == 0 && new_device_discovery) ++ blobmsg_add_string(bb, "manufacturerURL", elm_val ? elm_val : ""); ++ ++ if (elm_name && strcmp(elm_name, "modelDescription") == 0 && new_device_discovery) ++ blobmsg_add_string(bb, "modelDescription", elm_val ? elm_val : ""); ++ ++ if (elm_name && strcmp(elm_name, "modelName") == 0 && new_device_discovery) ++ blobmsg_add_string(bb, "modelName", elm_val ? elm_val : ""); ++ ++ if (elm_name && strcmp(elm_name, "modelNumber") == 0 && new_device_discovery) ++ blobmsg_add_string(bb, "modelNumber", elm_val ? elm_val : ""); ++ ++ if (elm_name && strcmp(elm_name, "modelURL") == 0 && new_device_discovery) ++ blobmsg_add_string(bb, "modelURL", elm_val ? elm_val : ""); ++ ++ if (elm_name && strcmp(elm_name, "serialNumber") == 0 && new_device_discovery) ++ blobmsg_add_string(bb, "serialNumber", elm_val ? elm_val : ""); ++ ++ if (elm_name && strcmp(elm_name, "UDN") == 0 && new_device_discovery) { ++ snprintf(buf, sizeof(buf), "%s", elm_val ? elm_val : ""); ++ blobmsg_add_string(bb, "UDN", buf); ++ } ++ ++ if (elm_name && strcmp(elm_name, "UPC") == 0 && new_device_discovery) ++ blobmsg_add_string(bb, "UPC", elm_val ? elm_val : ""); ++ ++ b = mxmlWalkNext(b, device, MXML_DESCEND); ++ } ++ ++ if (new_device_discovery && device_obj) ++ blobmsg_close_table(bb, device_obj); ++} ++ ++static void fill_service_element(struct blob_buf *bb, mxml_node_t *service) ++{ ++ mxml_node_t *b = service; ++ void *service_obj = NULL; ++ char buf[64] = {0}; ++ bool new_srv_discovery = false; ++ ++ while (b) { ++ ++ if (mxmlGetType(b) != MXML_ELEMENT) { ++ b = mxmlWalkNext(b, service, MXML_DESCEND); ++ continue; ++ } ++ ++ const char *elm_name = mxmlGetElement(b); ++ const char *elm_val = mxmlGetOpaque(b); ++ ++ if (elm_name && strcmp(elm_name, "UDN") == 0) ++ snprintf(buf, sizeof(buf), "%s", elm_val ? elm_val : ""); ++ ++ ++ if (elm_name && strcmp(elm_name, "service") == 0) { ++ ++ if (new_srv_discovery && service_obj) ++ blobmsg_close_table(bb, service_obj); ++ ++ service_obj = blobmsg_open_table(bb, NULL); ++ blobmsg_add_string(bb, "parent_dev", buf); ++ new_srv_discovery = true; ++ } ++ ++ if (elm_name && strcmp(elm_name, "serviceType") == 0 && new_srv_discovery) ++ blobmsg_add_string(bb, "serviceType", elm_val ? elm_val : ""); ++ ++ if (elm_name && strcmp(elm_name, "serviceId") == 0 && new_srv_discovery) ++ blobmsg_add_string(bb, "serviceId", elm_val ? elm_val : ""); ++ ++ if (elm_name && strcmp(elm_name, "SCPDURL") == 0 && new_srv_discovery) ++ blobmsg_add_string(bb, "SCPDURL", elm_val ? elm_val : ""); ++ ++ if (elm_name && strcmp(elm_name, "controlURL") == 0 && new_srv_discovery) ++ blobmsg_add_string(bb, "controlURL", elm_val ? elm_val : ""); ++ ++ if (elm_name && strcmp(elm_name, "eventSubURL") == 0 && new_srv_discovery) ++ blobmsg_add_string(bb, "eventSubURL", elm_val ? elm_val : ""); ++ ++ b = mxmlWalkNext(b, service, MXML_DESCEND); ++ } ++ ++ if (new_srv_discovery && service_obj) ++ blobmsg_close_table(bb, service_obj); ++} ++ ++static int upnp_description_res(struct ubus_context *ctx, struct ubus_object *obj __attribute__((unused)), ++ struct ubus_request_data *req, const char *method __attribute__((unused)), struct blob_attr *msg __attribute__((unused))) ++{ ++ struct desc_list_elt *desc_elt = NULL; ++ struct blob_buf bb = {0}; ++ ++ memset(&bb,0,sizeof(struct blob_buf)); ++ blob_buf_init(&bb, 0); ++ ++ void *desc_array = blobmsg_open_array(&bb, "descriptions"); ++ list_for_each_entry(desc_elt, &desc_list, list) { ++ void *device_obj = blobmsg_open_table(&bb, NULL); ++ blobmsg_add_string(&bb, "desc_url", desc_elt->url); ++ blobmsg_add_u32(&bb, "is_device_desc", desc_elt->is_device_desc); ++ blobmsg_close_table(&bb, device_obj); ++ ++ } ++ blobmsg_close_array(&bb, desc_array); ++ ++ list_for_each_entry(desc_elt, &desc_list, list) { ++ ++ FILE *fp = fopen(desc_elt->desc_path, "r"); ++ if (!fp) ++ continue; ++ ++ mxml_node_t *tree = mxmlLoadFile(NULL, fp, MXML_OPAQUE_CALLBACK); ++ fclose(fp); ++ ++ if (tree) { ++ void *devices_array = blobmsg_open_array(&bb, "devices"); ++ mxml_node_t *device = mxmlFindElement(tree, tree, "device", NULL, NULL, MXML_DESCEND); ++ fill_device_instances(&bb, device); ++ blobmsg_close_array(&bb, devices_array); ++ ++ void *services_array = blobmsg_open_array(&bb, "services"); ++ mxml_node_t *service = mxmlFindElement(tree, tree, "device", NULL, NULL, MXML_DESCEND); ++ fill_service_element(&bb, service); ++ blobmsg_close_array(&bb, services_array); ++ ++ mxmlDelete(tree); ++ } ++ } ++ ++ ubus_send_reply(ctx, req, bb.head); ++ blob_buf_free(&bb); ++ return 0; ++} ++ ++static struct ubus_method upnp_methods[] = { ++ UBUS_METHOD_NOARG("discovery", upnp_discovery_res), ++ UBUS_METHOD_NOARG("description", upnp_description_res), ++}; ++ ++static struct ubus_object_type upnp_type = UBUS_OBJECT_TYPE("upnp", upnp_methods); ++ ++static void upnp_discover_devices(struct uloop_timeout *timeout __attribute__((unused))) ++{ ++ __upnp_discover_devices(); ++} ++ ++static struct ubus_object upnp_object = { ++ .name = "upnp", ++ .type = &upnp_type, ++ .methods = upnp_methods, ++ .n_methods = ARRAY_SIZE(upnp_methods), ++}; ++ ++void upnp_thread_discover_devices(void) ++{ ++ struct ubus_context *ctx = NULL; ++ const char *ubus_socket = NULL; ++ int ret = 0; ++ ++ uloop_init(); ++ ++ ctx = ubus_connect(ubus_socket); ++ if (!ctx) { ++ syslog(LOG_ERR, "Failed to connect to ubus\n"); ++ return; ++ } ++ ++ ubus_add_uloop(ctx); ++ ++ __upnp_discover_devices(); ++ ++ ret = ubus_add_object(ctx, &upnp_object); ++ if (ret) { ++ syslog(LOG_ERR, "Failed to add 'upnp' ubus object: %s\n", ubus_strerror(ret)); ++ goto end; ++ } ++ ++ uloop_run(); ++ ++end: ++ free_all_desc_list(); ++ free_all_dev_list(); ++ uloop_done(); ++ ubus_free(ctx); ++} diff --git a/ssdpd/patches/012-ubus-runner-thread-hook b/ssdpd/patches/012-ubus-runner-thread-hook new file mode 100644 index 000000000..ec66d0487 --- /dev/null +++ b/ssdpd/patches/012-ubus-runner-thread-hook @@ -0,0 +1,70 @@ +--- a/minissdpd/minissdpd.c ++++ b/minissdpd/minissdpd.c +@@ -32,6 +32,8 @@ + #include + #include + #endif ++/* for uloop thread */ ++#include + + /* LOG_PERROR does not exist on Solaris */ + #ifndef LOG_PERROR +@@ -52,6 +54,9 @@ + #define MIN(x,y) (((x)<(y))?(x):(y)) + #endif + ++extern char *ssdp_sockpath; ++void upnp_thread_discover_devices(void); ++ + /* current request management structure */ + struct reqelem { + int socket; +@@ -1220,6 +1225,12 @@ static void ssdpDiscover(int s, int ipv6 + } + } + ++static void *thread_discover_devices(void *args __attribute__((unused))) ++{ ++ upnp_thread_discover_devices(); ++ return NULL; ++} ++ + /* main(): program entry point */ + int main(int argc, char * * argv) + { +@@ -1264,6 +1275,7 @@ int main(int argc, char * * argv) + unsigned char ttl = 2; /* UDA says it should default to 2 */ + const char * searched_device = NULL; /* if not NULL, search/filter a specific device type */ + int opt; ++ pthread_t upnp_thread; + + LIST_INIT(&reqlisthead); + LIST_INIT(&servicelisthead); +@@ -1309,6 +1321,7 @@ int main(int argc, char * * argv) + break; + case 's': + sockpath = optarg; ++ ssdp_sockpath = optarg; + break; + #ifndef NO_BACKGROUND_NO_PIDFILE + case 'p': +@@ -1496,6 +1509,11 @@ int main(int argc, char * * argv) + if(s_ssdp6 >= 0) + ssdpDiscover(s_ssdp6, 1, searched_device); + ++ int err = pthread_create(&upnp_thread, NULL, &thread_discover_devices, NULL); ++ if (err < 0) { ++ syslog(LOG_ERR, "Error when creating upnp thread"); ++ } ++ + /* Main loop */ + while(!quitting) { + /* fill readfds fd_set */ +@@ -1704,6 +1722,7 @@ quit: + if(unlink(pidfilename) < 0) + syslog(LOG_ERR, "unlink(%s): %m", pidfilename); + #endif ++ pthread_join(upnp_thread, NULL); + closelog(); + return ret; + }