Finish (mostly) upnp discovery

This commit is contained in:
Ron Pedde 2007-05-04 21:31:05 +00:00
parent 0eba96d92a
commit 648297e253
11 changed files with 472 additions and 49 deletions

View File

@ -15,18 +15,27 @@
<modelURL>http://www.fireflymediaserver.org/</modelURL>
<serialNumber>SERIAL-001</serialNumber>
<UDN>uuid:@upnp@</UDN>
<iconList>
<icon>
<mimetype>image/png</mimetype>
<width>32</width>
<height>32</height>
<depth>32</depth>
<url>/upnp/ff_logo_32.png</url>
</icon>
</iconList>
<serviceList>
<service>
<serviceType>urn:schemas-upnp-org:service:ConnectionManager:1</serviceType>
<serviceId>urn:upnp-org:serviceId:ConnectionManager</serviceId>
<SCPDURL>/upnp/cm/scpdurl</SCPDURL>
<SCPDURL>/upnp-cm.xml</SCPDURL>
<controlURL>/upnp/cm/control</controlURL>
<eventSubURL>/upnp/cm/event</eventSubURL>
</service>
<service>
<serviceType>urn:schemas-upnp-org:service:ContentDirectory:1</serviceType>
<serviceId>urn:upnp-org:serviceId:ContentDirectory</serviceId>
<SCPDURL>/upnp/cd/scpdurl</SCPDURL>
<SCPDURL>/upnp-cd.xml</SCPDURL>
<controlURL>/upnp/cd/control</controlURL>
<eventSubURL>/upnp/cd/event</eventSubURL>
</service>

178
admin-root/upnp-cd.xml Normal file
View File

@ -0,0 +1,178 @@
<?xml version="1.0" encoding="utf-8"?>
<scpd xmlns="urn:schemas-upnp-org:service-1-0">
<specVersion>
<major>1</major>
<minor>0</minor>
</specVersion>
<actionList>
<action>
<name>Browse</name>
<argumentList>
<argument>
<name>ObjectID</name>
<direction>in</direction>
<relatedStateVariable>A_ARG_TYPE_ObjectID</relatedStateVariable>
</argument>
<argument>
<name>BrowseFlag</name>
<direction>in</direction>
<relatedStateVariable>A_ARG_TYPE_BrowseFlag</relatedStateVariable>
</argument>
<argument>
<name>Filter</name>
<direction>in</direction>
<relatedStateVariable>A_ARG_TYPE_Filter</relatedStateVariable>
</argument>
<argument>
<name>StartingIndex</name>
<direction>in</direction>
<relatedStateVariable>A_ARG_TYPE_Index</relatedStateVariable>
</argument>
<argument>
<name>RequestedCount</name>
<direction>in</direction>
<relatedStateVariable>A_ARG_TYPE_Count</relatedStateVariable>
</argument>
<argument>
<name>SortCriteria</name>
<direction>in</direction>
<relatedStateVariable>A_ARG_TYPE_SortCriteria</relatedStateVariable>
</argument>
<argument>
<name>Result</name>
<direction>out</direction>
<relatedStateVariable>A_ARG_TYPE_Result</relatedStateVariable>
</argument>
<argument>
<name>NumberReturned</name>
<direction>out</direction>
<relatedStateVariable>A_ARG_TYPE_Count</relatedStateVariable>
</argument>
<argument>
<name>TotalMatches</name>
<direction>out</direction>
<relatedStateVariable>A_ARG_TYPE_Count</relatedStateVariable>
</argument>
<argument>
<name>UpdateID</name>
<direction>out</direction>
<relatedStateVariable>A_ARG_TYPE_UpdateID</relatedStateVariable>
</argument>
</argumentList>
</action>
<action>
<name>DestroyObject</name>
<argumentList>
<argument>
<name>ObjectID</name>
<direction>in</direction>
<relatedStateVariable>A_ARG_TYPE_ObjectID</relatedStateVariable>
</argument>
</argumentList>
</action>
<action>
<name>GetSystemUpdateID</name>
<argumentList>
<argument>
<name>Id</name>
<direction>out</direction>
<relatedStateVariable>SystemUpdateID</relatedStateVariable>
</argument>
</argumentList>
</action>
<action>
<name>GetSearchCapabilities</name>
<argumentList>
<argument>
<name>SearchCaps</name>
<direction>out</direction>
<relatedStateVariable>SearchCapabilities</relatedStateVariable>
</argument>
</argumentList>
</action>
<action>
<name>GetSortCapabilities</name>
<argumentList>
<argument>
<name>SortCaps</name>
<direction>out</direction>
<relatedStateVariable>SortCapabilities</relatedStateVariable>
</argument>
</argumentList>
</action>
<action>
<name>UpdateObject</name>
<argumentList>
<argument>
<name>ObjectID</name>
<direction>in</direction>
<relatedStateVariable>A_ARG_TYPE_ObjectID</relatedStateVariable>
</argument>
<argument>
<name>CurrentTagValue</name>
<direction>in</direction>
<relatedStateVariable>A_ARG_TYPE_TagValueList</relatedStateVariable>
</argument>
<argument>
<name>NewTagValue</name>
<direction>in</direction>
<relatedStateVariable>A_ARG_TYPE_TagValueList</relatedStateVariable>
</argument>
</argumentList>
</action>
</actionList>
<serviceStateTable>
<stateVariable sendEvents="no">
<name>A_ARG_TYPE_BrowseFlag</name>
<dataType>string</dataType>
<allowedValueList>
<allowedValue>BrowseMetadata</allowedValue>
<allowedValue>BrowseDirectChildren</allowedValue>
</allowedValueList>
</stateVariable>
<stateVariable sendEvents="yes">
<name>SystemUpdateID</name>
<dataType>ui4</dataType>
</stateVariable>
<stateVariable sendEvents="no">
<name>A_ARG_TYPE_Count</name>
<dataType>ui4</dataType>
</stateVariable>
<stateVariable sendEvents="no">
<name>A_ARG_TYPE_SortCriteria</name>
<dataType>string</dataType>
</stateVariable>
<stateVariable sendEvents="no">
<name>SortCapabilities</name>
<dataType>string</dataType>
</stateVariable>
<stateVariable sendEvents="no">
<name>A_ARG_TYPE_Index</name>
<dataType>ui4</dataType>
</stateVariable>
<stateVariable sendEvents="no">
<name>A_ARG_TYPE_ObjectID</name>
<dataType>string</dataType>
</stateVariable>
<stateVariable sendEvents="no">
<name>A_ARG_TYPE_UpdateID</name>
<dataType>ui4</dataType>
</stateVariable>
<stateVariable sendEvents="no">
<name>A_ARG_TYPE_TagValueList</name>
<dataType>string</dataType>
</stateVariable>
<stateVariable sendEvents="no">
<name>A_ARG_TYPE_Result</name>
<dataType>string</dataType>
</stateVariable>
<stateVariable sendEvents="no">
<name>SearchCapabilities</name>
<dataType>string</dataType>
</stateVariable>
<stateVariable sendEvents="no">
<name>A_ARG_TYPE_Filter</name>
<dataType>string</dataType>
</stateVariable>
</serviceStateTable>
</scpd>

132
admin-root/upnp-cm.xml Normal file
View File

@ -0,0 +1,132 @@
<?xml version="1.0" encoding="utf-8"?>
<scpd xmlns="urn:schemas-upnp-org:service-1-0">
<specVersion>
<major>1</major>
<minor>0</minor>
</specVersion>
<actionList>
<action>
<name>GetCurrentConnectionInfo</name>
<argumentList>
<argument>
<name>ConnectionID</name>
<direction>in</direction>
<relatedStateVariable>A_ARG_TYPE_ConnectionID</relatedStateVariable>
</argument>
<argument>
<name>RcsID</name>
<direction>out</direction>
<relatedStateVariable>A_ARG_TYPE_RcsID</relatedStateVariable>
</argument>
<argument>
<name>AVTransportID</name>
<direction>out</direction>
<relatedStateVariable>A_ARG_TYPE_AVTransportID</relatedStateVariable>
</argument>
<argument>
<name>ProtocolInfo</name>
<direction>out</direction>
<relatedStateVariable>A_ARG_TYPE_ProtocolInfo</relatedStateVariable>
</argument>
<argument>
<name>PeerConnectionManager</name>
<direction>out</direction>
<relatedStateVariable>A_ARG_TYPE_ConnectionManager</relatedStateVariable>
</argument>
<argument>
<name>PeerConnectionID</name>
<direction>out</direction>
<relatedStateVariable>A_ARG_TYPE_ConnectionID</relatedStateVariable>
</argument>
<argument>
<name>Direction</name>
<direction>out</direction>
<relatedStateVariable>A_ARG_TYPE_Direction</relatedStateVariable>
</argument>
<argument>
<name>Status</name>
<direction>out</direction>
<relatedStateVariable>A_ARG_TYPE_ConnectionStatus</relatedStateVariable>
</argument>
</argumentList>
</action>
<action>
<name>GetProtocolInfo</name>
<argumentList>
<argument>
<name>Source</name>
<direction>out</direction>
<relatedStateVariable>SourceProtocolInfo</relatedStateVariable>
</argument>
<argument>
<name>Sink</name>
<direction>out</direction>
<relatedStateVariable>SinkProtocolInfo</relatedStateVariable>
</argument>
</argumentList>
</action>
<action>
<name>GetCurrentConnectionIDs</name>
<argumentList>
<argument>
<name>ConnectionIDs</name>
<direction>out</direction>
<relatedStateVariable>CurrentConnectionIDs</relatedStateVariable>
</argument>
</argumentList>
</action>
</actionList>
<serviceStateTable>
<stateVariable sendEvents="no">
<name>A_ARG_TYPE_ProtocolInfo</name>
<dataType>string</dataType>
</stateVariable>
<stateVariable sendEvents="no">
<name>A_ARG_TYPE_ConnectionStatus</name>
<dataType>string</dataType>
<allowedValueList>
<allowedValue>OK</allowedValue>
<allowedValue>ContentFormatMismatch</allowedValue>
<allowedValue>InsufficientBandwidth</allowedValue>
<allowedValue>UnreliableChannel</allowedValue>
<allowedValue>Unknown</allowedValue>
</allowedValueList>
</stateVariable>
<stateVariable sendEvents="no">
<name>A_ARG_TYPE_AVTransportID</name>
<dataType>i4</dataType>
</stateVariable>
<stateVariable sendEvents="no">
<name>A_ARG_TYPE_RcsID</name>
<dataType>i4</dataType>
</stateVariable>
<stateVariable sendEvents="no">
<name>A_ARG_TYPE_ConnectionID</name>
<dataType>i4</dataType>
</stateVariable>
<stateVariable sendEvents="no">
<name>A_ARG_TYPE_ConnectionManager</name>
<dataType>string</dataType>
</stateVariable>
<stateVariable sendEvents="yes">
<name>SourceProtocolInfo</name>
<dataType>string</dataType>
</stateVariable>
<stateVariable sendEvents="yes">
<name>SinkProtocolInfo</name>
<dataType>string</dataType>
</stateVariable>
<stateVariable sendEvents="no">
<name>A_ARG_TYPE_Direction</name>
<dataType>string</dataType>
<allowedValueList>
<allowedValue>Input</allowedValue>
<allowedValue>Output</allowedValue>
</allowedValueList>
</stateVariable>
<stateVariable sendEvents="yes">
<name>CurrentConnectionIDs</name>
<dataType>string</dataType>
</stateVariable>
</serviceStateTable>
</scpd>

Binary file not shown.

After

Width:  |  Height:  |  Size: 362 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 606 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 838 B

View File

@ -1226,6 +1226,7 @@ int conf_write(void) {
FILE *fp;
if(!conf_main_file) {
DPRINTF(E_DBG,L_CONF,"Config file apparently not loaded\n");
return CONF_E_NOCONF;
}
@ -1233,10 +1234,13 @@ int conf_write(void) {
if((fp = fopen(conf_main_file,"w+")) != NULL) {
retval = _conf_write(fp,conf_main,0,NULL);
fclose(fp);
} else {
DPRINTF(E_LOG,L_CONF,"Error opening config file for write: %s\n",
strerror(errno));
}
util_mutex_unlock(l_conf);
return retval ? CONF_E_SUCCESS : CONF_E_NOTWRITABLE;
return retval;
}
/**
@ -1299,9 +1303,11 @@ int _conf_write(FILE *fp, LL *pll, int sublevel, char *parent) {
}
fprintf(fp,"\n");
if(!_conf_write(fp, pli->value.as_ll, 1, pli->key))
if(!_conf_write(fp, pli->value.as_ll, 1, pli->key)) {
DPRINTF(E_DBG,L_CONF,"Error writing key %s:%s\n",pli->key);
return FALSE;
}
}
break;
case LL_TYPE_INT:

View File

@ -531,7 +531,7 @@ int config_auth(WS_CONNINFO *pwsc, char *user, char *password) {
}
void config_emit_upnp(WS_CONNINFO *pwsc, void *value, char *arg) {
ws_writefd(pwsc,"%s",UPNP_UUID);
ws_writefd(pwsc,"%s",upnp_uuid());
}

View File

@ -589,10 +589,6 @@ int main(int argc, char *argv[]) {
config.reload = 1; /* force a reload on start */
while(!config.stop) {
#ifdef UPNP
upnp_tick(); /* run the upnp loop */
#endif
if((conf_get_int("general","rescan_interval",0) &&
(rescan_counter > conf_get_int("general","rescan_interval",0)))) {
if((conf_get_int("general","always_scan",0)) ||

View File

@ -40,6 +40,7 @@
#include "upnp.h"
#define UPNP_MAX_PACKET 1500
#define UPNP_CACHE_DURATION 1800
#define UPNP_ADDR "239.255.255.250"
#define UPNP_PORT 1900
@ -47,7 +48,9 @@
#define UPNP_TYPE_BYEBYE 2
#define UPNP_TYPE_RESPONSE 3
#define UPNP_SELECT_TIMEOUT 1 /* update loop for discover replies */
#define UPNP_UUID "12345678-1234-1234-1234-123456789013"
#define UPNP_SELECT_TIMEOUT 1
typedef struct upnp_adinfo_t {
int type; /** one of the UPNP_AD_ values */
@ -67,16 +70,24 @@ typedef struct upnp_packetinfo_t {
typedef struct upnp_disco_t {
int seconds_remaining;
char *query;
struct sockaddr to;
struct sockaddr_in to;
struct upnp_disco_t *next;
} UPNP_DISCO;
/* Globals */
UPNP_PACKETINFO upnp_packetlist;
OS_SOCKETTYPE upnp_socket;
UPNP_DISCO upnp_disco;
pthread_t upnp_listener_tid;
int upnp_quitflag = 0;
int upnp_thread_started = 0;
char *upnp_broadcast_types[] = {
"none",
"SSDP Alive",
"SSDP Byebye",
"SSDP Discovery Response"
};
/* Forwards */
int upnp_strcat(char *what, char *where, int bytes_left);
@ -85,6 +96,15 @@ void upnp_build_packet(char *packet, int len, int type, UPNP_PACKETINFO *pi,
void upnp_broadcast(int type, UPNP_DISCO *pdisco);
void *upnp_listener(void *arg);
void upnp_process_packet(void);
void upnp_process_queries(void);
/**
* return the current upnp uuid
*/
char *upnp_uuid(void) {
return UPNP_UUID;
}
/**
* add a upnp packet too the root list.
@ -178,11 +198,11 @@ void upnp_build_packet(char *packet, int len, int type,
/* USN & NT */
switch(pi->padinfo->type) {
case UPNP_AD_BARE:
snprintf(buffer,sizeof(buffer),"USN: uuid:%s\r\n",UPNP_UUID);
case UPNP_AD_UUID:
snprintf(buffer,sizeof(buffer),"USN: uuid:%s\r\n",upnp_uuid());
len=upnp_strcat(buffer,packet,len);
if(type != UPNP_TYPE_RESPONSE) { /* no NT for responses */
snprintf(buffer,sizeof(buffer),"NT: uuid:%s\r\n",UPNP_UUID);
snprintf(buffer,sizeof(buffer),"NT: uuid:%s\r\n",upnp_uuid());
len=upnp_strcat(buffer,packet,len);
}
break;
@ -190,7 +210,7 @@ void upnp_build_packet(char *packet, int len, int type,
case UPNP_AD_DEVICE:
case UPNP_AD_SERVICE:
case UPNP_AD_ROOT:
snprintf(buffer,sizeof(buffer),"USN: uuid:%s::%s:%s",UPNP_UUID,
snprintf(buffer,sizeof(buffer),"USN: uuid:%s::%s:%s",upnp_uuid(),
pi->padinfo->namespace, pi->padinfo->name);
len=upnp_strcat(buffer,packet,len);
if(pi->padinfo->version) {
@ -214,7 +234,9 @@ void upnp_build_packet(char *packet, int len, int type,
break;
}
len=upnp_strcat("CACHE-CONTROL: max-age=1800\r\n",packet,len);
snprintf(buffer,sizeof(buffer),"CACHE-CONTROL: max-age=%d\r\n",
UPNP_CACHE_DURATION);
len=upnp_strcat(buffer,packet,len);
if((pi->padinfo->body) && (type != UPNP_TYPE_RESPONSE)) {
snprintf(buffer,sizeof(buffer),"Content-Length: %d\r\n\r\n",
@ -234,41 +256,79 @@ void upnp_broadcast(int type, UPNP_DISCO *pdisco) {
struct sockaddr_in sin;
char packet[UPNP_MAX_PACKET];
int pass;
char passes;
int send_packet;
int elements=0;
int recognized=0;
char **argv = NULL;
memset(&sin, 0, sizeof(sin));
if(type == UPNP_TYPE_RESPONSE) {
memcpy((void*)&sin,(void*)&pdisco->to,sizeof(struct sockaddr_in));
} else {
sin.sin_addr.s_addr = inet_addr(UPNP_ADDR);
sin.sin_family = AF_INET;
sin.sin_port = htons(UPNP_PORT);
sin.sin_addr.s_addr = inet_addr(UPNP_ADDR);
}
util_mutex_lock(l_upnp);
DPRINTF(E_DBG,L_MISC,"Sending upnp broadcast of type: %d\n",type);
DPRINTF(E_DBG,L_MISC,"Sending upnp broadcast of type: %d (%s)\n",type,
upnp_broadcast_types[type]);
for(pass=0; pass < 2; pass++) {
passes = 2;
if(type == UPNP_TYPE_RESPONSE) {
passes = 1;
if(strcasecmp(pdisco->query,"ssdp:all")==0)
passes = 3;
elements = util_split(pdisco->query,":",&argv);
}
for(pass=0; pass < passes; pass++) {
pi=upnp_packetlist.next;
while(pi) {
send_packet = 1;
if(type == UPNP_TYPE_RESPONSE) {
send_packet = 0;
/* if it's a request for rootdevice, only respond
with root device. */
if(strcasecmp(pdisco->query,"ssdp:all")==0) {
send_packet = 1;
} else if((strcasecmp(pdisco->query,"upnp:rootdevice")==0) &&
(pi->padinfo->type == UPNP_AD_ROOT)) {
send_packet = 1;
}
else if((strncasecmp(pdisco->query,"uuid:",5) == 0) &&
(!strcasecmp((char*)&pdisco->query[5],upnp_uuid())) &&
(pi->padinfo->type == UPNP_AD_UUID)) {
send_packet = 1;
}
}
if(send_packet) {
usleep(100);
recognized = 1;
upnp_build_packet(packet, UPNP_MAX_PACKET, type, pi, pdisco);
sendto(upnp_socket,packet,strlen(packet),0,
(struct sockaddr *)&sin, sizeof(sin));
}
pi = pi->next;
}
}
util_mutex_unlock(l_upnp);
}
int upnp_tick(void) {
static time_t last_broadcast = 0;
if((time(NULL) - last_broadcast) > 60) {
upnp_broadcast(UPNP_TYPE_ALIVE,NULL);
last_broadcast = time(NULL);
if(type == UPNP_TYPE_RESPONSE) {
printf("%s respond to query %s\n", recognized ? "Did" : "Did not",
pdisco->query);
}
return TRUE;
}
if(argv) {
util_dispose_split(argv);
}
util_mutex_unlock(l_upnp);
}
/**
* start UPnP broadcaster. We'll want to send at least a rootdevice
@ -285,7 +345,9 @@ int upnp_init(void) {
srand((unsigned)time(NULL));
memset(&upnp_packetlist,0,sizeof(upnp_packetlist));
upnp_add_packet("base,", UPNP_AD_BARE, "/upnp-basic.xml",
memset(&upnp_disco,0,sizeof(upnp_disco));
upnp_add_packet("base,", UPNP_AD_UUID, "/upnp-basic.xml",
NULL, NULL, 0, NULL);
upnp_add_packet("base,", UPNP_AD_DEVICE, "/upnp-basic.xml",
"urn:schemas-upnp-org","MediaServer", 1, NULL);
@ -345,11 +407,42 @@ int upnp_init(void) {
return TRUE;
}
/**
* walk the query chain and see if there are any queries we need
* to respond to.
*/
void upnp_process_queries(void) {
UPNP_DISCO *plast, *pcurrent;
plast=&upnp_disco;
pcurrent = upnp_disco.next;
/* don't need locks here since this ist he same thread as the listening
* thread */
while(pcurrent) {
if(pcurrent->seconds_remaining - UPNP_SELECT_TIMEOUT < 1) {
/* do the broadcast, and cull it */
upnp_broadcast(UPNP_TYPE_RESPONSE,pcurrent);
pcurrent = pcurrent->next;
free(plast->next->query);
free(plast->next);
plast->next = pcurrent;
} else {
pcurrent->seconds_remaining -= UPNP_SELECT_TIMEOUT;
plast = pcurrent;
pcurrent = pcurrent->next;
}
}
}
/**
* listener thread for upnp query requests
*/
void *upnp_listener(void *arg) {
int result;
static time_t last_broadcast=0;
fd_set readset;
struct timeval timeout;
@ -379,9 +472,20 @@ void *upnp_listener(void *arg) {
upnp_thread_started=0;
pthread_exit(NULL);
}
if(FD_ISSET(upnp_socket,&readset)) {
upnp_process_packet();
}
if((time(NULL) - last_broadcast) > (UPNP_CACHE_DURATION/3)) {
DPRINTF(E_DBG,L_MISC,"Time for alive message!\n");
upnp_broadcast(UPNP_TYPE_ALIVE,NULL);
last_broadcast = time(NULL);
}
upnp_process_queries();
}
upnp_thread_started=0;
DPRINTF(E_DBG,L_MISC,"upnp listener exiting\n");
pthread_exit(NULL);
@ -464,19 +568,22 @@ void upnp_process_packet(void) {
/* do we care? Let's check the query and see if it is interesting */
discovery = 0;
pdisco = (UPNP_DISCO *)malloc(sizeof(UPNP_DISCO));
if(!pdisco)
DPRINTF(E_FATAL,L_MISC,"malloc error");
memset(pdisco,0,sizeof(UPNP_DISCO));
if(mx) {
pdisco->seconds_remaining = 2;
pdisco->seconds_remaining = (rand() / (RAND_MAX/mx)) + 1;
DPRINTF(E_DBG,L_MISC,"Responding in %d (of %d) seconds\n",
pdisco->seconds_remaining, mx);
}
pdisco->query = query;
query = NULL;
memcpy((void*)&pdisco->to, (void*)&from, sizeof(struct sockaddr));
memcpy((void*)&pdisco->to, (void*)&from,sizeof(struct sockaddr_in));
pdisco->next = upnp_disco.next;
upnp_disco.next = pdisco;
}
if(query) {
@ -510,6 +617,3 @@ int upnp_deinit(void) {
return TRUE;
}

View File

@ -31,18 +31,16 @@
#ifndef _UPNP_H_
#define _UPNP_H_
#define UPNP_UUID "12345678-1234-1234-1234-123456789013"
#define UPNP_AD_DEVICE 1
#define UPNP_AD_SERVICE 2
#define UPNP_AD_ROOT 3
#define UPNP_AD_BARE 4
#define UPNP_AD_UUID 4
extern int upnp_init(void);
extern int upnp_tick(void);
extern int upnp_deinit(void);
extern void upnp_add_packet(char *group_id, int type, char *location,
char *namespace, char *name, int version,
char *body);
extern char *upnp_uuid();
#endif /* _UPNP_H_ */