minissdpd: add a notification mode. Bump to version 1.5

fixes #139
This commit is contained in:
Thomas Bernard 2016-01-13 16:07:09 +01:00
parent 78ab294de9
commit 9e24d3cccb
12 changed files with 353 additions and 84 deletions

View File

@ -6,3 +6,4 @@ listifaces
Makefile.bak
validateminissdpd
validatecodelength
showminissdpdnotif

View File

@ -1,5 +1,10 @@
$Id: Changelog.txt,v 1.43 2015/08/06 14:05:49 nanard Exp $
VERSION 1.5:
2016/01/13:
add "notification" mode (command 5)
2015/08/06:
disable multicast loop
add -f command line option to filter for a specific device type

View File

@ -31,12 +31,16 @@ ifeq ($(DEB_BUILD_ARCH_OS), kfreebsd)
endif
#EXECUTABLES = minissdpd testminissdpd listifaces
EXECUTABLES = minissdpd testminissdpd testcodelength
EXECUTABLES = minissdpd testminissdpd testcodelength \
showminissdpdnotif
MINISSDPDOBJS = minissdpd.o openssdpsocket.o daemonize.o upnputils.o \
ifacewatch.o getroute.o getifaddr.o asyncsendto.o
TESTMINISSDPDOBJS = testminissdpd.o
TESTMINISSDPDOBJS = testminissdpd.o printresponse.o
SHOWMINISSDPDNOTIFOBJS = showminissdpdnotif.o printresponse.o
ALLOBJS = $(MINISSDPDOBJS) $(TESTMINISSDPDOBJS) testcodelength.o
ALLOBJS = $(MINISSDPDOBJS) $(TESTMINISSDPDOBJS) \
$(SHOWMINISSDPDNOTIFOBJS) \
testcodelength.o
INSTALLPREFIX ?= $(PREFIX)/usr
SBININSTALLDIR = $(INSTALLPREFIX)/sbin
@ -76,6 +80,8 @@ minissdpd: $(MINISSDPDOBJS)
testminissdpd: $(TESTMINISSDPDOBJS)
showminissdpdnotif: $(SHOWMINISSDPDNOTIFOBJS)
testcodelength: testcodelength.o
listifaces: listifaces.o upnputils.o
@ -96,4 +102,8 @@ ifacewatch.o: config.h openssdpsocket.h minissdpdtypes.h upnputils.h
getroute.o: getroute.h upnputils.h
getifaddr.o: config.h getifaddr.h
asyncsendto.o: asyncsendto.h upnputils.h
testminissdpd.o: codelength.h printresponse.h
printresponse.o: codelength.h
showminissdpdnotif.o: codelength.h printresponse.h
printresponse.o: codelength.h
testcodelength.o: codelength.h

View File

@ -29,6 +29,7 @@ close unix socket connection.
2 - USN (unique id)
3 - everything
4 - submit service (see below)
5 - switch connection to notification mode
n bytes : string length : 1 byte if < 128 else the upper bit indicate that
one additional byte should be read, etc. (see codelength.h)
n bytes = string
@ -39,8 +40,9 @@ request type 0 (version) :
n bytes string length
n bytes = version string
request type 1 / 2 / 3 :
1st byte : number of services/devices
request type 1 / 2 / 3 / 5 :
1st byte : number of services/devices, from 0 to 254.
255 is a special value, see below
For each service/device :
URL :
n bytes string length
@ -52,6 +54,12 @@ USN:
n bytes string length
n bytes = identifier string
if the 1st byte is 255, the format is as follows :
1st byte = 255
2nd byte = notification type (1=NEW, 2=UPDATE, 3=REMOVE)
3rd byte = number of services/devices, from 0 to 255.
request type 4 = submit service
1st byte = 4
(k,n) bytes : length and string "ST" (service type)

View File

@ -15,7 +15,7 @@ indique s'il existe un octet suplementaire, etc...
n octets = chaine
format reponse :
1 octet : nombre de reponses
1 octet : nombre de reponses (de 0 à 254)
pour chaque rep :
URL :
n octets longueur de la chaine
@ -27,7 +27,16 @@ USN:
n octets longueur de la chaine
n octets = chaine identifiant
Type de requete 4 = submit service
si le 1er octet est 255, alors le format est le suivant :
1 octet : 255
1 octet : type de notification
1 = NOTIF_NEW, 2 = NOTIF_NEW, 3 = NOTIF_REMOVE
1 octet : nombre de reponses (0 à 255)
puis comme ci dessus pour chaque réponse
* Type de requete 4 = submit service
1 octet = 4
(k,n) octets : longueur et chaine "ST" (service type)
(k,n) octets : longueur et chaine "USN"
@ -35,3 +44,6 @@ Type de requete 4 = submit service
(k,n) octets : longueur et chaine "Location"
Pas de reponse
* Type de requete 5 = mode notification
Reste connecté et reçoit au fur et à mesure les nouvelles connections
réponses au format normal

View File

@ -1 +1 @@
1.4
1.5

View File

@ -46,6 +46,7 @@
/* current request management stucture */
struct reqelem {
int socket;
int is_notify; /* has subscribed to notifications */
LIST_ENTRY(reqelem) entries;
unsigned char * output_buffer;
int output_buffer_offset;
@ -69,6 +70,16 @@ struct device {
char data[];
};
/* Services stored for answering to M-SEARCH */
struct service {
char * st; /* Service type */
char * usn; /* Unique identifier */
char * server; /* Server string */
char * location; /* URL */
LIST_ENTRY(service) entries;
};
LIST_HEAD(servicehead, service) servicelisthead;
#define NTS_SSDP_ALIVE 1
#define NTS_SSDP_BYEBYE 2
#define NTS_SSDP_UPDATE 3
@ -93,6 +104,16 @@ unsigned int upnp_configid = 1337;
/* LAN interfaces/addresses */
struct lan_addr_list lan_addrs;
/* connected clients */
LIST_HEAD(reqstructhead, reqelem) reqlisthead;
/* functions prototypes */
#define NOTIF_NEW 1
#define NOTIF_UPDATE 2
#define NOTIF_REMOVE 3
static void
sendNotifications(int notif_type, const struct device * dev, const struct service * serv);
/* functions */
@ -296,6 +317,7 @@ updateDevice(const struct header * headers, time_t t)
}
memcpy(p->data + p->headers[0].l + p->headers[1].l,
headers[2].p, headers[2].l);
/* TODO : check p->headers[HEADER_LOCATION].l */
return 0;
}
pp = &p->next;
@ -324,6 +346,7 @@ updateDevice(const struct header * headers, time_t t)
pc += headers[i].l;
}
devlist = p;
sendNotifications(NOTIF_NEW, p, NULL);
}
return 1;
}
@ -346,6 +369,7 @@ removeDevice(const struct header * headers)
&& (0==memcmp(p->headers[HEADER_USN].p, headers[HEADER_USN].p, headers[HEADER_USN].l)) )
{
syslog(LOG_INFO, "remove device : %.*s", headers[HEADER_USN].l, headers[HEADER_USN].p);
sendNotifications(NOTIF_REMOVE, p, NULL);
*pp = p->next;
free(p);
return -1;
@ -357,6 +381,68 @@ removeDevice(const struct header * headers)
return 0;
}
/* sent notifications to client having subscribed */
static void
sendNotifications(int notif_type, const struct device * dev, const struct service * serv)
{
struct reqelem * req;
unsigned int m;
unsigned char rbuf[RESPONSE_BUFFER_SIZE];
unsigned char * rp;
for(req = reqlisthead.lh_first; req; req = req->entries.le_next) {
if(!req->is_notify) continue;
rbuf[0] = '\xff'; /* special code for notifications */
rbuf[1] = (unsigned char)notif_type;
rbuf[2] = 0;
rp = rbuf + 3;
if(dev) {
/* response :
* 1 - Location
* 2 - NT (device/service type)
* 3 - usn */
m = dev->headers[HEADER_LOCATION].l;
CODELENGTH(m, rp);
memcpy(rp, dev->headers[HEADER_LOCATION].p, dev->headers[HEADER_LOCATION].l);
rp += dev->headers[HEADER_LOCATION].l;
m = dev->headers[HEADER_NT].l;
CODELENGTH(m, rp);
memcpy(rp, dev->headers[HEADER_NT].p, dev->headers[HEADER_NT].l);
rp += dev->headers[HEADER_NT].l;
m = dev->headers[HEADER_USN].l;
CODELENGTH(m, rp);
memcpy(rp, dev->headers[HEADER_USN].p, dev->headers[HEADER_USN].l);
rp += dev->headers[HEADER_USN].l;
rbuf[2]++;
}
if(serv) {
/* response :
* 1 - Location
* 2 - NT (device/service type)
* 3 - usn */
m = strlen(serv->location);
CODELENGTH(m, rp);
memcpy(rp, serv->location, m);
rp += m;
m = strlen(serv->st);
CODELENGTH(m, rp);
memcpy(rp, serv->st, m);
rp += m;
m = strlen(serv->usn);
CODELENGTH(m, rp);
memcpy(rp, serv->usn, m);
rp += m;
rbuf[2]++;
}
if(rbuf[2] > 0) {
if(write_or_buffer(req, rbuf, rp - rbuf) < 0) {
syslog(LOG_ERR, "(s=%d) write: %m", req->socket);
/*goto error;*/
}
}
}
}
/* SendSSDPMSEARCHResponse() :
* build and send response to M-SEARCH SSDP packets. */
static void
@ -404,16 +490,6 @@ SendSSDPMSEARCHResponse(int s, const struct sockaddr * sockname,
}
}
/* Services stored for answering to M-SEARCH */
struct service {
char * st; /* Service type */
char * usn; /* Unique identifier */
char * server; /* Server string */
char * location; /* URL */
LIST_ENTRY(service) entries;
};
LIST_HEAD(servicehead, service) servicelisthead;
/* Process M-SEARCH requests */
static void
processMSEARCH(int s, const char * st, int st_len,
@ -775,7 +851,8 @@ void processRequest(struct reqelem * req)
l, (unsigned)n);
goto error;
}
if(l == 0 && type != MINISSDPD_SEARCH_ALL && type != MINISSDPD_GET_VERSION) {
if(l == 0 && type != MINISSDPD_SEARCH_ALL
&& type != MINISSDPD_GET_VERSION && type != MINISSDPD_NOTIF) {
syslog(LOG_WARNING, "bad request (length=0, type=%d)", type);
goto error;
}
@ -867,7 +944,7 @@ void processRequest(struct reqelem * req)
goto error;
}
break;
case 4: /* submit service */
case MINISSDPD_SUBMIT: /* submit service */
newserv = malloc(sizeof(struct service));
if(!newserv) {
syslog(LOG_ERR, "cannot allocate memory");
@ -963,8 +1040,17 @@ void processRequest(struct reqelem * req)
}
/* Inserting new service */
LIST_INSERT_HEAD(&servicelisthead, newserv, entries);
sendNotifications(NOTIF_NEW, NULL, newserv);
newserv = NULL;
break;
case MINISSDPD_NOTIF: /* switch socket to notify */
rbuf[0] = '\0';
if(write_or_buffer(req, rbuf, 1) < 0) {
syslog(LOG_ERR, "(s=%d) write: %m", req->socket);
goto error;
}
req->is_notify = 1;
break;
default:
syslog(LOG_WARNING, "Unknown request type %d", type);
rbuf[0] = '\0';
@ -1076,7 +1162,6 @@ int main(int argc, char * * argv)
#endif /* ENABLE_IPV6 */
int s_unix = -1; /* unix socket communicating with clients */
int s_ifacewatch = -1; /* socket to receive Route / network interface config changes */
LIST_HEAD(reqstructhead, reqelem) reqlisthead;
struct reqelem * req;
struct reqelem * reqnext;
fd_set readfds;

87
minissdpd/printresponse.c Normal file
View File

@ -0,0 +1,87 @@
/* $Id: $ */
/* Project : miniupnp
* website : http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/
* Author : Thomas BERNARD
* copyright (c) 2005-2016 Thomas Bernard
* This software is subjet to the conditions detailed in the
* provided LICENCE file. */
#include <stdio.h>
#include "codelength.h"
#define NOTIF_NEW 1
#define NOTIF_UPDATE 2
#define NOTIF_REMOVE 3
void printresponse(const unsigned char * resp, int n)
{
int i, l;
int notif_type;
unsigned int nresp;
const unsigned char * p;
if(n == 0)
return;
/* first, hexdump the response : */
for(i = 0; i < n; i += 16) {
printf("%06x | ", i);
for(l = i; l < n && l < (i + 16); l++)
printf("%02x ", resp[l]);
while(l < (i + 16)) {
printf(" ");
l++;
}
printf("| ");
for(l = i; l < n && l < (i + 16); l++)
putchar((resp[l] >= ' ' && resp[l] < 128) ? resp[l] : '.');
putchar('\n');
}
/* now parse and display all devices of response */
nresp = resp[0]; /* 1st byte : number of devices in response */
if(nresp == 0xff) {
/* notification */
notif_type = resp[1];
nresp = resp[2];
printf("Notification : ");
switch(notif_type) {
case NOTIF_NEW: printf("new\n"); break;
case NOTIF_UPDATE: printf("update\n"); break;
case NOTIF_REMOVE: printf("remove\n"); break;
default: printf("**UNKNOWN**\n");
}
p = resp + 3;
} else {
p = resp + 1;
}
for(i = 0; i < (int)nresp; i++) {
if(p >= resp + n)
goto error;
/*l = *(p++);*/
DECODELENGTH(l, p);
if(p + l > resp + n)
goto error;
printf("%d - %.*s\n", i, l, p); /* URL */
p += l;
if(p >= resp + n)
goto error;
/*l = *(p++);*/
DECODELENGTH(l, p);
if(p + l > resp + n)
goto error;
printf(" %.*s\n", l, p); /* ST */
p += l;
if(p >= resp + n)
goto error;
/*l = *(p++);*/
DECODELENGTH(l, p);
if(p + l > resp + n)
goto error;
printf(" %.*s\n", l, p); /* USN */
p += l;
}
return;
error:
printf("*** WARNING : TRUNCATED RESPONSE ***\n");
}

12
minissdpd/printresponse.h Normal file
View File

@ -0,0 +1,12 @@
/* $Id: $ */
/* Project : miniupnp
* Author : Thomas BERNARD
* copyright (c) 2016 Thomas Bernard
* This software is subjet to the conditions detailed in the
* provided LICENCE file. */
#ifndef PRINTRESPONSE_H_INCLUDED
#define PRINTRESPONSE_H_INCLUDED
void printresponse(const unsigned char * resp, int n);
#endif /* PRINTRESPONSE_H_INCLUDED */

View File

@ -0,0 +1,77 @@
/* $Id: $ */
/* MiniUPnP project
* (c) 2016 Thomas Bernard
* website : http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/
* This software is subject to the conditions detailed
* in the LICENCE file provided within the distribution */
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include "codelength.h"
#include "printresponse.h"
static volatile sig_atomic_t quitting = 0;
static void sighandler(int sig)
{
(void)sig;
quitting = 1;
}
int main(int argc, char * * argv)
{
int i;
int s;
struct sockaddr_un addr;
const char * sockpath = "/var/run/minissdpd.sock";
unsigned char buffer[4096];
ssize_t n;
const char command5[] = { 0x05, 0x00 };
struct sigaction sa;
for(i=0; i<argc-1; i++) {
if(0==strcmp(argv[i], "-s"))
sockpath = argv[++i];
}
/* set signal handlers */
memset(&sa, 0, sizeof(struct sigaction));
sa.sa_handler = sighandler;
if(sigaction(SIGINT, &sa, NULL)) {
fprintf(stderr, "Failed to set SIGINT handler.\n");
}
s = socket(AF_UNIX, SOCK_STREAM, 0);
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, sockpath, sizeof(addr.sun_path));
if(connect(s, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0) {
fprintf(stderr, "connecting to %s : ", addr.sun_path);
perror("connect");
return 1;
}
printf("connected to %s\n", addr.sun_path);
n = write(s, command5, sizeof(command5)); /* NOTIF command */
printf("%d bytes written\n", (int)n);
while(!quitting) {
n = read(s, buffer, sizeof(buffer));
if(n < 0) {
if(errno == EINTR) continue;
perror("read");
break;
}
printf("%d bytes read\n", (int)n);
printresponse(buffer, (int)n);
}
printf("Quit...\n");
close(s);
return 0;
}

View File

@ -2,7 +2,7 @@
/* Project : miniupnp
* website : http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/
* Author : Thomas BERNARD
* copyright (c) 2005-2015 Thomas Bernard
* copyright (c) 2005-2016 Thomas Bernard
* This software is subjet to the conditions detailed in the
* provided LICENCE file. */
@ -14,9 +14,8 @@
#include <sys/socket.h>
#include <sys/un.h>
#define DECODELENGTH(n, p) n = 0; \
do { n = (n << 7) | (*p & 0x7f); } \
while(*(p++)&0x80);
#include "codelength.h"
#include "printresponse.h"
void printversion(const unsigned char * resp, int n)
{
@ -31,61 +30,6 @@ void printversion(const unsigned char * resp, int n)
printf("MiniSSDPd version : %.*s\n", l, p);
}
void printresponse(const unsigned char * resp, int n)
{
int i, l;
unsigned int nresp;
const unsigned char * p;
if(n == 0)
return;
/* first, hexdump the response : */
for(i = 0; i < n; i += 16) {
printf("%06x | ", i);
for(l = i; l < n && l < (i + 16); l++)
printf("%02x ", resp[l]);
while(l < (i + 16)) {
printf(" ");
l++;
}
printf("| ");
for(l = i; l < n && l < (i + 16); l++)
putchar((resp[l] >= ' ' && resp[l] < 128) ? resp[l] : '.');
putchar('\n');
}
/* now parse and display all devices of response */
nresp = resp[0]; /* 1st byte : number of devices in response */
p = resp + 1;
for(i = 0; i < (int)nresp; i++) {
if(p >= resp + n)
goto error;
/*l = *(p++);*/
DECODELENGTH(l, p);
if(p + l > resp + n)
goto error;
printf("%d - %.*s\n", i, l, p); /* URL */
p += l;
if(p >= resp + n)
goto error;
/*l = *(p++);*/
DECODELENGTH(l, p);
if(p + l > resp + n)
goto error;
printf(" %.*s\n", l, p); /* ST */
p += l;
if(p >= resp + n)
goto error;
/*l = *(p++);*/
DECODELENGTH(l, p);
if(p + l > resp + n)
goto error;
printf(" %.*s\n", l, p); /* USN */
p += l;
}
return;
error:
printf("*** WARNING : TRUNCATED RESPONSE ***\n");
}
#define SENDCOMMAND(command, size) write(s, command, size); \
printf("Command written type=%u\n", (unsigned char)command[0]);
@ -110,16 +54,17 @@ int connect_unix_socket(const char * sockpath)
int
main(int argc, char * * argv)
{
char command0[] = { 0x00, 0x00 };
const char command0[] = { 0x00, 0x00 };
char command1[] = "\x01\x00urn:schemas-upnp-org:device:InternetGatewayDevice";
char command2[] = "\x02\x00uuid:fc4ec57e-b051-11db-88f8-0060085db3f6::upnp:rootdevice";
char command3[] = { 0x03, 0x00 };
const char command3[] = { 0x03, 0x00 };
/* old versions of minissdpd would reject a command with
* a zero length string argument */
char command3compat[] = "\x03\x00ssdp:all";
char command4[] = "\x04\x00test:test:test";
char bad_command[] = { 0xff, 0xff };
char overflow[] = { 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
const char bad_command[] = { 0xff, 0xff };
const char overflow[] = { 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
const char command5[] = { 0x05, 0x00 };
int s;
int i;
void * tmp;
@ -226,6 +171,15 @@ main(int argc, char * * argv)
n = read(s, buf, sizeof(buf));
printf("Response received %d bytes\n", (int)n);
printresponse(buf, n);
if(n == 0) {
close(s);
s = connect_unix_socket(sockpath);
}
n = SENDCOMMAND(command5, sizeof(command5));
n = read(s, buf, sizeof(buf));
printf("Response received %d bytes\n", (int)n);
printresponse(buf, n);
close(s);
return 0;

18
minissdpd/testminissdpdnotif.sh Executable file
View File

@ -0,0 +1,18 @@
#!/bin/sh
# $Id: $
# (c) 2016 Thomas Bernard
OS=`uname -s`
IF=lo
if [ "$OS" = "Darwin" ] ; then
IF=lo0
fi
# if set, 1st argument is network interface
if [ -n "$1" ] ; then
IF=$1
fi
SOCKET=`mktemp -t minissdpdsocketXXXXXX`
PID="${SOCKET}.pid"
./minissdpd -s $SOCKET -p $PID -i $IF || exit 1
./showminissdpdnotif -s $SOCKET || exit 2
kill `cat $PID`