/* (C) 2012-14 Digital Devices GmbH. This file is part of the octoserve SAT>IP server. Octoserve is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Octoserve is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with octoserve. If not, see . */ #include "octoserve.h" extern struct rtsp_error rtsp_errors; extern uint32_t debug; static void ssdp_reset_setup(struct os_ssdp *ss) { ss->setup = 0; mtime(&ss->setupt); ss->setupt += 5; mtime(&ss->annt); } static int read_id(char *type, uint32_t *id) { int fd, len; char name[80]; len = snprintf(name, 80, "/config/%s.id", type); if (len < 0 || len == 80) return -1; fd = open(name, O_RDONLY); if (fd < 0) return -1; printf("read id from %s\n", name); len = read(fd, name, 80); name[len] = 0; *id = strtol(name, NULL, 10); printf("%s.id = %d\n", type, *id); return 0; } static int write_id(char *type, uint32_t id) { int fd, len; char name[80]; len = snprintf(name, 80, "/config/%s.id", type); if (len < 0 || len == 80) return -1; fd = open(name, O_WRONLY|O_CREAT|O_TRUNC, 00644); if (fd < 0) return -1; printf("write id = %d to %s\n", id, name); len = snprintf(name, 80, "%d", id); write(fd, name, len); close(fd); return 0; } static int ssdp_msg(struct os_ssdp *ss, char *buf, int n, int notify, int nr, int v6, int send_id) { int len; uint8_t *u = ss->uuid; char uuid[37]; char *header = notify ? "NOTIFY * HTTP/1.1\r\nHOST: 239.255.255.250:1900\r\n" : "HTTP/1.1 200 OK\r\n"; char id[32]; if (send_id) { len = snprintf(id, sizeof(id), "DEVICEID.SES.COM: %d", ss->devid); if (len < 0 || len >= sizeof(id)) return -1; } else id[0] = 0; if (ss->alive) { len = snprintf(buf, n, "%s" "CACHE-CONTROL: max-age=1800\r\n" "LOCATION: http://%s%s\r\n" "NT: %s\r\n" "NTS: ssdp:%s\r\n" "SERVER: %s\r\n" "USN: %s%s%s\r\n" "BOOTID.UPNP.ORG: %d\r\n" "CONFIGID.UPNP.ORG: %d\r\n" "%s\r\n" "\r\n", header, v6 ? ss->ip6 : ss->ip, ss->dev[nr].loc, ss->dev[nr].nt[0] ? ss->dev[nr].nt : ss->dev[nr].uuid, "alive", ss->dev[nr].server, ss->dev[nr].uuid, ss->dev[nr].nt[0] ? "::" : "", ss->dev[nr].nt, ss->bootid, ss->configid, id ); } else { dbgprintf(DEBUG_SSDP, "BYEBYE\n"); len = snprintf(buf, n, "%s" "NT: %s\r\n" "NTS: ssdp:byebye\r\n" "USN: %s%s%s\r\n" "BOOTID.UPNP.ORG: %d\r\n" "CONFIGID.UPNP.ORG: %d\r\n" "\r\n", header, ss->dev[nr].nt[0] ? ss->dev[nr].nt : ss->dev[nr].uuid, ss->dev[nr].uuid, ss->dev[nr].nt[0] ? "::" : "", ss->dev[nr].nt, ss->bootid, ss->configid ); } return len; } static int sendto_port(int sock, const void *buf, size_t len, struct sockaddr_in *cadr, uint16_t port) { struct sockaddr_in adr = *cadr; adr.sin_port = htons(port); return sendto(sock, buf, len, 0, (struct sockaddr *) &adr, sizeof(adr)); } static int send_ssdp_msg(struct os_ssdp *ss, int nr, int notify) { uint8_t buf[2048]; int len; len = ssdp_msg(ss, buf, sizeof(buf), notify, nr, 0, nr < 3); if (len < 0 || len >= sizeof(buf)) return -1; if (notify) { struct sockaddr_in insadr; memset(&insadr, 0, sizeof(insadr)); insadr.sin_family = AF_INET; insadr.sin_port = htons(1900); insadr.sin_addr.s_addr = inet_addr("239.255.255.250"); return sendto(ss->sock, buf, len, 0, (struct sockaddr *) &insadr, sizeof(insadr)); } else return sendto(ss->sock, buf, len, 0, &ss->cadr, sizeof(struct sockaddr)); } static int ssdp_announce(struct octoserve *os, int alive, int notify) { struct os_ssdp *ss = &os->ssdp; uint8_t buf[2048]; int len, nr, res; int s = os->ssdp.sock; uint8_t *u = ss->uuid; time_t t; if (notify) { mtime(&t); if (!ss->setup) if (t >= ss->setupt) { ss->setup = 1; dbgprintf(DEBUG_SSDP, "END SSDP SETUP\n"); } if (ss->alive == alive && t < ss->annt) return 0; ss->alive = alive; ss->annt = t + 900; //FIXME: +/- random } for (nr = 0; nr < ss->dev_num; nr++) { send_ssdp_msg(ss, nr, notify); } } static int ssdp_defend(struct os_ssdp *ss) { char buf[2048], host[64]; int len; int s = ss->v6 ? ss->sock6 : ss->sock; sockname(&ss->cadr, host); dbgprintf(DEBUG_SSDP, "defend against HOST:%s\n", host); len = snprintf(buf, sizeof(buf), "M-SEARCH * HTTP/1.1\r\n" "HOST: %s:1900\r\n" "MAN: \"ssdp:discover\"\r\n" "ST: urn:ses-com:device:SatIPServer:1\r\n" "USER-AGENT: %s\r\n" "DEVICEID.SES.COM: %d\r\n" "\r\n", ss->v6 ? ss->ip6 : ss->ip, ss->server, ss->devid); if (len < 0 || len >= sizeof(buf)) return -1; //return sendto(s, buf, len, 0, &ss->cadr, sizeof(ss->cadr)); return sendto_port(ss->sock, buf, len, &ss->cadr, ss->csport); } static int send_reply_msearch(struct os_ssdp *ss, int mc, int send_id, int nr) { char buf[2048], id[32]; int len, res; int v6 = ss->v6; int s = v6 ? ss->sock6 : ss->sock; dbgprintf(DEBUG_SSDP, "send msearch reply nr=%d\n", nr); if (send_id) { len = snprintf(id, sizeof(id), "DEVICEID.SES.COM: %d\r\n", ss->devid); if (len < 0 || len >= sizeof(id)) return -1; } else id[0] = 0; len = snprintf(buf, sizeof(buf), "HTTP/1.1 200 OK\r\n" "CACHE-CONTROL: max-age=1800\r\n" "EXT:\r\n" "LOCATION: http://%s%s\r\n" "SERVER: %s\r\n" "ST: %s\r\n" "USN: %s%s%s\r\n" "BOOTID.UPNP.ORG: %d\r\n" "CONFIGID.UPNP.ORG: %d\r\n" "%s" "\r\n", ss->v6 ? ss->ip6 : ss->ip, ss->dev[nr].loc, ss->dev[nr].server, ss->dev[nr].nt[0] ? ss->dev[nr].nt : ss->dev[nr].uuid, ss->dev[nr].uuid, ss->dev[nr].nt[0] ? "::" : "", ss->dev[nr].nt, ss->bootid, ss->configid, id ); if (len < 0 || len >= sizeof(buf)) return -1; dbgprintf(DEBUG_SSDP, "sending:\n%s", buf); if (mc) { struct sockaddr_in insadr; memset(&insadr, 0, sizeof(insadr)); insadr.sin_family = AF_INET; insadr.sin_port = htons(1900); insadr.sin_addr.s_addr = inet_addr("239.255.255.250"); res = sendto(s, buf, len, 0, (struct sockaddr *) &insadr, sizeof(insadr)); } else res = sendto(s, buf, len, 0, &ss->cadr, sizeof(struct sockaddr)); //res = sendto_port(ss->sock, buf, len, &ss->cadr, ss->csport); return res; } static int ssdp_msearch(struct os_ssdp *ss, int mc, char *st) { int nr, len = strlen(st), type = 0; uint8_t *u = ss->uuid; if (!len) return 0; if (!strncasecmp(st, "ssdp:all", 8)) type = 1; else if (!strncasecmp(st, "uuid:", 5)) type = 2; dbgprintf(DEBUG_SSDP, "msearch mc=%u, st=%s (type=%u)\n", mc, st, type); for (nr = 0; nr < ss->dev_num; nr++) { if ((type == 1) || ((type == 2) && !ss->dev[nr].nt[0] && !strncasecmp(ss->dev[nr].uuid, st, len)) || ((type == 0) && !strncasecmp(ss->dev[nr].nt, st, len))) send_reply_msearch(ss, mc, nr < 3, nr); } return 0; } static void handle_ssdp(struct octoserve *os, char *m, int ml) { struct os_ssdp *ss = &os->ssdp; int ln, ll, tl, as, al; char *l, *le, *me = m + ml, *st = NULL; int type = 0; int htype = 0; int devid = -1; int mx = -1; uint8_t uu[16]; char host[64]; char str[INET_ADDRSTRLEN]; int sport = 1900, hport = 1900; dbgprintf(DEBUG_SSDP, "\n********************************************************************************\n"); inet_ntop(AF_INET, &((struct sockaddr_in *) &ss->cadr)->sin_addr, str, INET_ADDRSTRLEN); dbgprintf(DEBUG_SSDP, "\nSSDP: %s\n", str); memset(uu, 0, 16); for (l = m, ln = -1; l < me; l += ll + 1) { for (le = l; *le != '\r' && *le != '\n' && le < me; le++); ll = le - l; if (!ll) continue; *le = 0; ln++; dbgprintf(DEBUG_SSDP, "%02d: %s\n", ln, l); if (ln == 0) { if (!strncasecmp(l, "M-SEARCH * HTTP/1.1", ll)) { type = 1; continue; } if (!strncasecmp(l, "NOTIFY * HTTP/1.1", ll)) { type = 2; continue; } if (!strncasecmp(l, "HTTP/1.1 200 OK", ll)) { type = 3; continue; } dbgprintf(DEBUG_SSDP, "unknown header\n"); return; } for (tl = 0; tl < ll && l[tl] != ':'; tl++); if (tl == ll) continue; for (as = tl + 1; as < ll && isspace(l[as]); as++); al = ll - as; if (tl == 4 && !strncasecmp(l, "HOST", 4)) { char *h, *p; if (!strncmp(l + as, "239.255.255.250:1900", al)) htype = 1; else { htype = 2; if (mx < 0) mx = 1; } h = host; p = l + as; while (*p && *p != ':') *(h++) = *(p++); *h = 0; p++; hport = atoi(p); } if (tl == 2 && !strncasecmp(l, "MX", tl)) { mx = atoi(l + as); continue; } if (tl == 2 && !strncasecmp(l, "ST", tl)) { st = l + as; continue; } if (tl == 16 && !memcmp(l, "DEVICEID.SES.COM", tl)) { devid = atoi(l + as); //dbgprintf(DEBUG_SSDP, "sat>ip device id %d\n", devid); continue; } if (tl = 19 && !memcmp(l, "SEARCHPORT.UPNP.ORG", tl)) { sport = atoi(l + as); dbgprintf(DEBUG_SSDP, "searchport %d\n", sport); continue; } if (tl == 3 && !memcmp(l, "USN", tl) && !memcmp(l+5, "uuid:", 5)) { int n; //printf("USN: %s\n", l+5); sscanf(l+10, "%2hhx%2hhx%2hhx%2hhx-%2hhx%2hhx-" "%2hhx%2hhx-%2hhx%2hhx-%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%n", &uu[0], &uu[1], &uu[2], &uu[3], &uu[4], &uu[5], &uu[6], &uu[7], &uu[8], &uu[9], &uu[10], &uu[11], &uu[12], &uu[13], &uu[14], &uu[15], &n); /* ignore message from myself */ if (!memcmp(uu, ss->uuid, 16)) dbgprintf(DEBUG_SSDP, "that's me!\n"); continue; } } ss->csport = sport; dbgprintf(DEBUG_SSDP, "host=%s, hport=%u, type=%d, htype=%d mx=%d\n", host, hport, type, htype, mx); /* M-SEARCH */ if (ss->setup) { if (type == 1 && mx > 0) { //ssdp_msearch(ss, htype == 2 ? 0 : 1, st); ssdp_msearch(ss, 0, st); } /* NOTIFY */ if (type == 2 && devid == ss->devid && memcmp(uu, ss->uuid, 16)) ssdp_defend(ss); } else { if (type == 1 && htype == 2 && ss->devid == devid) { send_reply_msearch(ss, 0, 1, 2); ss->devid++; write_id("device", ss->devid); dbgprintf(DEBUG_SSDP, "new device id %d \n", ss->devid); ssdp_reset_setup(ss); } } } #define SSDP_MCAST "239.255.255.250" #define SSDP_MCAST_LL "FF02::C" #define SSDP_MCAST_SL "FF05::C" static int ssdp_mc_add(int s, const char *ifname, int add, int v6) { if (v6) { struct ipv6_mreq imr; unsigned int ifindex = if_nametoindex(ifname); memset(&imr, 0, sizeof(imr)); inet_pton(AF_INET6, SSDP_MCAST_LL, &imr.ipv6mr_multiaddr); imr.ipv6mr_interface = ifindex; if (setsockopt(s, IPPROTO_IPV6, add ? IPV6_JOIN_GROUP : IPV6_LEAVE_GROUP, &imr, sizeof(imr)) < 0) return -1; inet_pton(AF_INET6, SSDP_MCAST_SL, &imr.ipv6mr_multiaddr); if (setsockopt(s, IPPROTO_IPV6, add ? IPV6_JOIN_GROUP : IPV6_LEAVE_GROUP, &imr, sizeof(imr)) < 0) return -1; } else { struct ip_mreq imr; struct sockaddr_in sadr; unsigned char ttl = 2, one = 1, zero = 0; get_ifa(ifname, AF_INET, (struct sockaddr *) &sadr); imr.imr_multiaddr.s_addr = inet_addr(SSDP_MCAST); imr.imr_interface.s_addr = sadr.sin_addr.s_addr; if (setsockopt(s, IPPROTO_IP, add ? IP_ADD_MEMBERSHIP : IP_DROP_MEMBERSHIP, &imr, sizeof(imr)) < 0) return -1; setsockopt(s, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)); //setsockopt(s, IPPROTO_IP, IP_MULTICAST_LOOP, &zero, sizeof(zero)); } printf("mc_add OK\n"); return 0; } static int ssdpsock(const char *ifnam, int family, struct sockaddr *sadr) { int one=1, sock; struct addrinfo *ais, *ai, hints = { .ai_flags = AI_PASSIVE, .ai_family = family, .ai_socktype = SOCK_DGRAM, .ai_protocol = 0, .ai_addrlen = 0, .ai_addr = sadr, .ai_canonname = NULL, .ai_next = NULL, }; if (getaddrinfo(NULL, "1900", &hints, &ais) < 0) return -1; for (ai = ais; ai; ai = ai->ai_next) { sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); if (sock == -1) continue; //dump(ai->ai_addr, sizeof(*ai->ai_addr)); if (!setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) && !bind(sock, ai->ai_addr, ai->ai_addrlen)) break; close(sock); } freeaddrinfo(ais); ssdp_mc_add(sock, ifnam, 1, family == AF_INET6 ? 1 : 0); return sock; } static void add_fd(int fd, int *mfd, fd_set *fds) { FD_SET(fd, fds); if (fd > *mfd) *mfd = fd; } static void ssdp_loop(struct octoserve *os) { struct os_ssdp *ss = &os->ssdp; int num; int mfd; fd_set fds; struct timeval timeout; char buf[1501]; ss->http_sock = streamsock("8888", AF_INET, &ss->http_sadr); if (listen(ss->http_sock, 10) < 0) { printf("listen error"); return; } ssdp_reset_setup(ss); while (!os->exit) { ssdp_announce(os, 1, 1); timeout.tv_sec = 1; timeout.tv_usec = 0; FD_ZERO(&fds); mfd = 0; add_fd(ss->sock, &mfd, &fds); //add_fd(ss->sock6, &mfd, &fds); add_fd(ss->http_sock, &mfd, &fds); num = select(mfd + 1, &fds, NULL, NULL, &timeout); if (num < 0) break; if (FD_ISSET(ss->sock, &fds)) { socklen_t len; int n; len = sizeof(ss->cadr); n = recvfrom(ss->sock, buf, sizeof(buf) - 1, 0, &ss->cadr, &len); ss->v6 = 0; if (n > 0) handle_ssdp(os, buf, n); } if (FD_ISSET(ss->http_sock, &fds)) { socklen_t len; pthread_t pt; //ss->csock = accept4(ss->http_sock, &ss->cadr, &len, SOCK_NONBLOCK); ss->csock = accept(ss->http_sock, &ss->cadr, &len); handle_http(ss); } } ssdp_announce(os, 0, 1); } static void ssdp_search(int s) { struct sockaddr_in insadr; char ms[] = "M-SEARCH * HTTP/1.1\r\nHOST: 239.255.255.250:1900\r\n" "MAN: \"ssdp:discover\"\r\nMX: 5\r\nST: ssdp:all\r\n\r\n"; memset(&insadr, 0, sizeof(insadr)); insadr.sin_family = AF_INET; insadr.sin_port = htons(1900); insadr.sin_addr.s_addr = inet_addr("239.255.255.250"); sendto(s, &ms[0], sizeof(ms) - 1, 0, (struct sockaddr *) &insadr, sizeof(insadr)); } void ssdp_thread(struct octoserve *os) { //ssdp_search(s); ssdp_loop(os); } static uint64_t get_us(void) { struct timeval tv; gettimeofday(&tv, 0); return ((uint64_t) tv.tv_sec * 1000000ULL + (uint64_t) tv.tv_usec); } static void create_uuid(uint8_t *u, uint8_t *mac) { uint8_t uu[10] = { 0xdd, 0x84, 0x8d, 0x00, 0x26, 0xec, 0x11, 0xeb, 0x80, 0x00 }; memcpy(u, uu, 10); memcpy(u + 10, mac, 6); } static void create_uuid_old(uint8_t *u, uint8_t *mac) { uint64_t us, ns; int i; memcpy(u + 10, mac, 6); us = get_us(); ns = us * 1000ULL + 122192928000000000ULL; u[0] = ns >> 24; u[1] = ns >> 16; u[2] = ns >> 8; u[3] = ns; u[4] = ns >> 40; u[5] = ns >> 32; u[6] = ns >> 56; u[7] = ns >> 48; u[8] = 0; u[9] = 0; u[6] = (u[6] & 0x0f) | 0x10; u[8] = (u[8] & 0x3f) | 0x80; } static int add_dev(struct os_ssdp *ss, char *nt, char *loc, char *uuid, char *server) { int n = ss->dev_num; if (n == UPNP_DEV_MAX) return -1; ss->dev[n].nt = nt; ss->dev[n].loc = loc; ss->dev[n].uuid = uuid; ss->dev[n].server = server; ss->dev_num++; } int init_ssdp(struct octoserve *os, struct os_ssdp *ss, uint32_t d, int nossdp, int nodms) { time_t t; uint8_t *u, mac[6]; struct ifreq ifr; uint32_t id; int stat; char *dms = "Linux/3.17.7 DLNADOC/1.50 UPnP/1.0 OctopusNet-DMS/1.0"; debug = d; ss->os = os; ss->server = "Linux/3.17.7 UPnP/1.1 OctopusNet/" OCTOSERVE_VERSION; stat = read_id("device", &id); if (stat < 0) { ss->devid = 5; write_id("device", ss->devid); } else ss->devid = id; stat = read_id("boot", &id); if (stat < 0) ss->bootid = 0; else ss->bootid = id + 1; write_id("boot", ss->bootid); ss->configid = 1; get_ifa(os->ifname, AF_INET, &os->ssdp.sadr); get_ifa(os->ifname, AF_INET6, &os->ssdp.sadr6); sockname(&os->ssdp.sadr, os->ssdp.ip); sockname(&os->ssdp.sadr6, os->ssdp.ip6); if (!nossdp) { ss->sock = ssdpsock(os->ifname, AF_INET, &os->ssdp.sadr); if (ss->sock < 0) return -1; ss->sock6 = ssdpsock(os->ifname, AF_INET6, &os->ssdp.sadr); if (ss->sock6 < 0) return -1; strcpy(ifr.ifr_name, os->ifname); ioctl(ss->sock, SIOCGIFHWADDR, &ifr); memcpy(mac, ifr.ifr_hwaddr.sa_data, 6); create_uuid(ss->uuid, mac); u = ss->uuid; snprintf(ss->uuid_str, 42, "uuid:%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", u[0], u[1], u[2], u[3], u[4], u[5], u[6], u[7], u[8], u[9], u[10], u[11], u[12], u[13], u[14], u[15]); snprintf(ss->uuid_str2, 42, "uuid:f0287290-e1e1-11e2-9a21-%02x%02x%02x%02x%02x%02x", u[10], u[11], u[12], u[13], u[14], u[15]); add_dev(ss, "upnp:rootdevice", "/octoserve/octonet.xml", ss->uuid_str, ss->server); add_dev(ss, "", "/octoserve/octonet.xml", ss->uuid_str, ss->server); add_dev(ss, "urn:ses-com:device:SatIPServer:1", "/octoserve/octonet.xml", ss->uuid_str, ss->server); if (!nodms) { add_dev(ss, "upnp:rootdevice", ":8080/dms.xml", ss->uuid_str2, dms); add_dev(ss, "", ":8080/dms.xml", ss->uuid_str2, dms); add_dev(ss, "urn:schemas-upnp-org:device:MediaServer:1", ":8080/dms.xml", ss->uuid_str2, dms); add_dev(ss, "urn:schemas-upnp-org:service:ConnectionManager:1", ":8080/dms.xml", ss->uuid_str2, dms); add_dev(ss, "urn:schemas-upnp-org:service:ContentDirectory:1", ":8080/dms.xml", ss->uuid_str2, dms); add_dev(ss, "urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1", ":8080/dms.xml", ss->uuid_str2, dms); } pthread_create(&os->ssdp.pt, NULL, (void *) ssdp_thread, os); } return 0; }