More UPnP work. Almost finished with discovery.

This commit is contained in:
Ron Pedde 2007-05-03 03:30:30 +00:00
parent 0115887492
commit 0eba96d92a
3 changed files with 318 additions and 65 deletions

View File

@ -423,10 +423,6 @@ int main(int argc, char *argv[]) {
VERSION,err_getlevel()); VERSION,err_getlevel());
#ifdef UPNP
upnp_init();
#endif
/* load plugins before we drop privs? Maybe... let the /* load plugins before we drop privs? Maybe... let the
* plugins do stuff they might need to */ * plugins do stuff they might need to */
plugin_init(); plugin_init();
@ -476,6 +472,10 @@ int main(int argc, char *argv[]) {
free(runas); free(runas);
#ifdef UPNP
upnp_init();
#endif
/* this will require that the db be readable by the runas user */ /* this will require that the db be readable by the runas user */
db_type = conf_alloc_string("general","db_type","sqlite"); db_type = conf_alloc_string("general","db_type","sqlite");
db_parms = conf_alloc_string("general","db_parms","/var/cache/mt-daapd"); db_parms = conf_alloc_string("general","db_parms","/var/cache/mt-daapd");
@ -497,7 +497,7 @@ int main(int argc, char *argv[]) {
/* Initialize the database before starting */ /* Initialize the database before starting */
DPRINTF(E_LOG,L_MAIN|L_DB,"Initializing database\n"); DPRINTF(E_LOG,L_MAIN|L_DB,"Initializing database\n");
if(db_init(&reload)) { if(db_init(reload)) {
DPRINTF(E_FATAL,L_MAIN|L_DB,"Error in db_init: %s\n",strerror(errno)); DPRINTF(E_FATAL,L_MAIN|L_DB,"Error in db_init: %s\n",strerror(errno));
} }
@ -641,6 +641,10 @@ int main(int argc, char *argv[]) {
} }
#endif #endif
#ifdef UPNP
upnp_deinit();
#endif
/* Got to find a cleaner way to stop the web server. /* Got to find a cleaner way to stop the web server.
* Closing the fd of the socking accepting doesn't necessarily * Closing the fd of the socking accepting doesn't necessarily

View File

@ -1,4 +1,4 @@
/* /**
* $Id: $ * $Id: $
* *
* Implementation of functions for UPnP discovery * Implementation of functions for UPnP discovery
@ -20,10 +20,12 @@
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/ */
#include <errno.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <arpa/inet.h> #include <arpa/inet.h>
#include <netinet/in.h> #include <netinet/in.h>
#include <pthread.h>
#include <string.h> #include <string.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <sys/stat.h> #include <sys/stat.h>
@ -41,47 +43,80 @@
#define UPNP_ADDR "239.255.255.250" #define UPNP_ADDR "239.255.255.250"
#define UPNP_PORT 1900 #define UPNP_PORT 1900
#define UPNP_TYPE_ALIVE 1
#define UPNP_TYPE_BYEBYE 2
#define UPNP_TYPE_RESPONSE 3
#define UPNP_SELECT_TIMEOUT 1 /* update loop for discover replies */
typedef struct upnp_adinfo_t {
int type; /** one of the UPNP_AD_ values */
int version; /** numeric version. 0 if unversioned */
char *location; /** url path to descriptor. e.g. /upnp-device.xml */
char *namespace; /** usually "upnp" or "urn:schemas-upnp-org" */
char *name; /** MediaServer, ConnectionManager, rootdevice, etc */
char *body; /** body of a alive packet (postinfo) */
} UPNP_ADINFO;
typedef struct upnp_packetinfo_t { typedef struct upnp_packetinfo_t {
char *group_id; char *group_id;
char *base_packet; UPNP_ADINFO *padinfo;
char *location;
char *usn;
char *nt;
char *body;
struct upnp_packetinfo_t *next; struct upnp_packetinfo_t *next;
} UPNP_PACKETINFO; } UPNP_PACKETINFO;
typedef struct upnp_disco_t {
int seconds_remaining;
char *query;
struct sockaddr to;
struct upnp_disco_t *next;
} UPNP_DISCO;
/* Globals */ /* Globals */
UPNP_PACKETINFO upnp_packetlist; UPNP_PACKETINFO upnp_packetlist;
OS_SOCKETTYPE upnp_socket; OS_SOCKETTYPE upnp_socket;
pthread_t upnp_listener_tid;
int upnp_quitflag = 0;
int upnp_thread_started = 0;
/* Forwards */ /* Forwards */
int upnp_strcat(char *what, char *where, int bytes_left); int upnp_strcat(char *what, char *where, int bytes_left);
void upnp_build_packet(char *packet, int len, UPNP_PACKETINFO *pi); void upnp_build_packet(char *packet, int len, int type, UPNP_PACKETINFO *pi,
void upnp_broadcast(void); UPNP_DISCO *pdisco);
void upnp_broadcast(int type, UPNP_DISCO *pdisco);
void *upnp_listener(void *arg);
void upnp_process_packet(void);
/** /**
* add a upnp packet too the root list. * add a upnp packet too the root list.
*/ */
void upnp_add_packet(char *group_id, char *location, void upnp_add_packet(char *group_id, int type, char *location,
char *usn, char *nt, char *body) { char *namespace, char *name, int version,
char *body) {
UPNP_PACKETINFO *pnew; UPNP_PACKETINFO *pnew;
UPNP_ADINFO *pnewinfo;
pnew = (UPNP_PACKETINFO *)malloc(sizeof(UPNP_PACKETINFO)); pnew = (UPNP_PACKETINFO *)malloc(sizeof(UPNP_PACKETINFO));
if(!pnew) if(!pnew)
DPRINTF(E_FATAL,L_MISC,"Malloc error\n"); DPRINTF(E_FATAL,L_MISC,"Malloc error\n");
pnewinfo = (UPNP_ADINFO *)malloc(sizeof(UPNP_ADINFO));
if(!pnewinfo)
DPRINTF(E_FATAL,L_MISC,"Malloc error\n");
memset(pnew,0,sizeof(UPNP_PACKETINFO)); memset(pnew,0,sizeof(UPNP_PACKETINFO));
memset(pnewinfo,0,sizeof(UPNP_ADINFO));
if(group_id) if(group_id)
pnew->group_id = strdup(group_id); pnew->group_id = strdup(group_id);
if(location)
pnew->location = strdup(location); pnewinfo->type = type;
if(usn) pnewinfo->version = version;
pnew->usn = strdup(usn); if(location) pnewinfo->location = strdup(location);
if(nt) if(namespace) pnewinfo->namespace = strdup(namespace);
pnew->nt = strdup(nt); if(name) pnewinfo->name = strdup(name);
if(body) if(body) pnewinfo->body = strdup(body);
pnew->body = strdup(body);
pnew->padinfo = pnewinfo;
util_mutex_lock(l_upnp); util_mutex_lock(l_upnp);
pnew->next = upnp_packetlist.next; pnew->next = upnp_packetlist.next;
@ -102,7 +137,8 @@ int upnp_strcat(char *what, char *where, int bytes_left) {
} }
void upnp_build_packet(char *packet, int len, UPNP_PACKETINFO *pi) { void upnp_build_packet(char *packet, int len, int type,
UPNP_PACKETINFO *pi, UPNP_DISCO *pdisco) {
char buffer[256]; char buffer[256];
char hostname[256]; char hostname[256];
*packet = '\0'; *packet = '\0';
@ -110,41 +146,81 @@ void upnp_build_packet(char *packet, int len, UPNP_PACKETINFO *pi) {
port = conf_get_int("general","port",0); port = conf_get_int("general","port",0);
len = upnp_strcat("NOTIFY * HTTP/1.1\r\n",packet,len); if(type == UPNP_TYPE_RESPONSE) {
if(pi->location) { len = upnp_strcat("HTTP/1.1 200 OK\r\n",packet,len);
} else {
len = upnp_strcat("NOTIFY * HTTP/1.1\r\n",packet,len);
}
if(pi->padinfo->location) {
gethostname(hostname,sizeof(hostname)); gethostname(hostname,sizeof(hostname));
snprintf(buffer,sizeof(buffer),"LOCATION: http://%s:%d%s\r\n", snprintf(buffer,sizeof(buffer),"LOCATION: http://%s:%d%s\r\n",
hostname,port,pi->location); hostname,port,pi->padinfo->location);
len=upnp_strcat(buffer,packet,len); len=upnp_strcat(buffer,packet,len);
} }
len=upnp_strcat("HOST: 239.255.255.250:1900\r\n",packet,len);
if(type != UPNP_TYPE_RESPONSE)
len=upnp_strcat("HOST: 239.255.255.250:1900\r\n",packet,len);
len=upnp_strcat("SERVER: POSIX, UPnP/1.0, Firefly/" VERSION "\r\n", len=upnp_strcat("SERVER: POSIX, UPnP/1.0, Firefly/" VERSION "\r\n",
packet,len); packet,len);
len=upnp_strcat("NTS: ssdp:alive\r\n",packet,len);
/* USN */ if(type == UPNP_TYPE_ALIVE) {
len=upnp_strcat("USN:uuid:",packet,len); len=upnp_strcat("NTS: ssdp:alive\r\n",packet,len);
len=upnp_strcat(UPNP_UUID,packet,len); } else if(type == UPNP_TYPE_BYEBYE) {
if(pi->usn) { len=upnp_strcat("NTS: ssdp:byebye\r\n",packet,len);
snprintf(buffer,sizeof(buffer),"::%s",pi->usn); } else if ((type == UPNP_TYPE_RESPONSE) && (pdisco)) {
len=upnp_strcat(buffer,packet,len); /* we need the original ST */
snprintf(buffer,sizeof(buffer),"ST: %s\r\n",pdisco->query);
len = upnp_strcat(buffer,packet,len);
}
/* USN & NT */
switch(pi->padinfo->type) {
case UPNP_AD_BARE:
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);
len=upnp_strcat(buffer,packet,len);
}
break;
case UPNP_AD_DEVICE:
case UPNP_AD_SERVICE:
case UPNP_AD_ROOT:
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) {
snprintf(buffer,sizeof(buffer),"%d",pi->padinfo->version);
len=upnp_strcat(buffer,packet,len);
}
len=upnp_strcat("\r\n",packet,len);
if(type != UPNP_TYPE_RESPONSE) { /* no NT for responses */
snprintf(buffer,sizeof(buffer),"NT: %s:%s",
pi->padinfo->namespace, pi->padinfo->name);
len=upnp_strcat(buffer,packet,len);
if(pi->padinfo->version) {
snprintf(buffer,sizeof(buffer),"%d",pi->padinfo->version);
len=upnp_strcat(buffer,packet,len);
}
len=upnp_strcat("\r\n",packet,len);
}
break;
default:
break;
} }
len=upnp_strcat("\r\n",packet,len);
len=upnp_strcat("CACHE-CONTROL: max-age=1800\r\n",packet,len); len=upnp_strcat("CACHE-CONTROL: max-age=1800\r\n",packet,len);
if(pi->nt) {
snprintf(buffer,sizeof(buffer),"NT:%s\r\n",pi->nt);
} else {
snprintf(buffer,sizeof(buffer),"NT:uuid:%s\r\n", UPNP_UUID);
}
len=upnp_strcat(buffer,packet,len);
if(pi->body) { if((pi->padinfo->body) && (type != UPNP_TYPE_RESPONSE)) {
snprintf(buffer,sizeof(buffer),"Content-Length: %d\r\n\r\n", snprintf(buffer,sizeof(buffer),"Content-Length: %d\r\n\r\n",
(int)strlen(pi->body)); (int)strlen(pi->padinfo->body));
len=upnp_strcat(buffer,packet,len); len=upnp_strcat(buffer,packet,len);
len=upnp_strcat(pi->body,packet,len); len=upnp_strcat(pi->padinfo->body,packet,len);
} else { } else {
len=upnp_strcat("Content-Length: 0\r\n\r\n",packet,len); len=upnp_strcat("Content-Length: 0\r\n\r\n",packet,len);
} }
@ -153,7 +229,7 @@ void upnp_build_packet(char *packet, int len, UPNP_PACKETINFO *pi) {
/** /**
* broadcast out the upnp packets * broadcast out the upnp packets
*/ */
void upnp_broadcast(void) { void upnp_broadcast(int type, UPNP_DISCO *pdisco) {
UPNP_PACKETINFO *pi; UPNP_PACKETINFO *pi;
struct sockaddr_in sin; struct sockaddr_in sin;
char packet[UPNP_MAX_PACKET]; char packet[UPNP_MAX_PACKET];
@ -166,10 +242,12 @@ void upnp_broadcast(void) {
util_mutex_lock(l_upnp); util_mutex_lock(l_upnp);
DPRINTF(E_DBG,L_MISC,"Sending upnp broadcast of type: %d\n",type);
for(pass=0; pass < 2; pass++) { for(pass=0; pass < 2; pass++) {
pi=upnp_packetlist.next; pi=upnp_packetlist.next;
while(pi) { while(pi) {
upnp_build_packet(packet, UPNP_MAX_PACKET, pi); upnp_build_packet(packet, UPNP_MAX_PACKET, type, pi, pdisco);
sendto(upnp_socket,packet,strlen(packet),0, sendto(upnp_socket,packet,strlen(packet),0,
(struct sockaddr *)&sin, sizeof(sin)); (struct sockaddr *)&sin, sizeof(sin));
pi = pi->next; pi = pi->next;
@ -184,7 +262,7 @@ int upnp_tick(void) {
static time_t last_broadcast = 0; static time_t last_broadcast = 0;
if((time(NULL) - last_broadcast) > 60) { if((time(NULL) - last_broadcast) > 60) {
upnp_broadcast(); upnp_broadcast(UPNP_TYPE_ALIVE,NULL);
last_broadcast = time(NULL); last_broadcast = time(NULL);
} }
@ -200,24 +278,23 @@ int upnp_init(void) {
int ttl = 3; int ttl = 3;
int reuse=1; int reuse=1;
int result; int result;
int err;
struct sockaddr_in addr; struct sockaddr_in addr;
struct ip_mreq mreq; struct ip_mreq mreq;
srand((unsigned)time(NULL));
memset(&upnp_packetlist,0,sizeof(upnp_packetlist)); memset(&upnp_packetlist,0,sizeof(upnp_packetlist));
upnp_add_packet("base","/upnp-basic.xml",NULL,NULL,NULL); upnp_add_packet("base,", UPNP_AD_BARE, "/upnp-basic.xml",
upnp_add_packet("base","/upnp-basic.xml", NULL, NULL, 0, NULL);
"urn:schemas-upnp-org:device:MediaServer:1", upnp_add_packet("base,", UPNP_AD_DEVICE, "/upnp-basic.xml",
"urn:schemas-upnp-org:device:MediaServer:1",NULL); "urn:schemas-upnp-org","MediaServer", 1, NULL);
upnp_add_packet("base","/upnp-basic.xml", upnp_add_packet("base,", UPNP_AD_SERVICE, "/upnp-basic.xml",
"urn:schemas-upnp-org:service:AVTransport:1", "urn:schemas-upnp-org","ContentDirectory", 1, NULL);
"urn:schemas-upnp-org:service:AVTransport:1",NULL); upnp_add_packet("base,", UPNP_AD_SERVICE, "/upnp-basic.xml",
upnp_add_packet("base","/upnp-basic.xml", "urn:schemas-upnp-org","ConnectionManager", 1, NULL);
"urn:schemas-upnp-org:service:ContentDirectory:1", upnp_add_packet("base,", UPNP_AD_ROOT, "/upnp-basic.xml",
"urn:schemas-upnp-org:service:ContentDirectory:1",NULL); "upnp","rootdevice",0,NULL);
upnp_add_packet("basic","/upnp-basic.xml",
"urn:schemas-upnp-org:service:ConnectionManager:1",
"urn:schemas-upnp-org:service:ConnectionManager:1",NULL);
upnp_add_packet("base","/upnp-basic.xml","upnp:rootdevice","upnp:rootdevice",NULL);
upnp_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); upnp_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
result = setsockopt(upnp_socket, IPPROTO_IP, IP_MULTICAST_TTL, result = setsockopt(upnp_socket, IPPROTO_IP, IP_MULTICAST_TTL,
@ -256,15 +333,181 @@ int upnp_init(void) {
return FALSE; return FALSE;
} }
/* got the socket, let's start up the listener thread */
DPRINTF(E_DBG,L_MISC,"Starting UPnP listener thread\n");
if((err=pthread_create(&upnp_listener_tid,NULL,upnp_listener,NULL))) {
DPRINTF(E_LOG,L_MISC,"Warning: could not start upnp listener: %s\n",
strerror(err));
upnp_thread_started=0;
return TRUE;
}
return TRUE; return TRUE;
} }
/**
* listener thread for upnp query requests
*/
void *upnp_listener(void *arg) {
int result;
fd_set readset;
struct timeval timeout;
upnp_thread_started = 1;
DPRINTF(E_DBG,L_MISC,"upnp listener thread started\n");
/* sit in a tight select loop until upnp_deinit sets the
* quitflag */
while(!upnp_quitflag) {
FD_ZERO(&readset);
FD_SET(upnp_socket,&readset); /* win32? */
/* this is kind of lame, but will defend from an OS
* that doesn't return a select error when an underlying
* file handle is closed */
timeout.tv_sec=UPNP_SELECT_TIMEOUT;
timeout.tv_usec=0;
while((result=select(upnp_socket+1,&readset,NULL,NULL,&timeout)==-1) &&
(errno = EINTR)) {
}
if(result == -1) {
/* this is a real error */
DPRINTF(E_LOG,L_MISC,"Error in select in upnp listener: %s\n",
strerror(errno));
upnp_thread_started=0;
pthread_exit(NULL);
}
upnp_process_packet();
}
upnp_thread_started=0;
DPRINTF(E_DBG,L_MISC,"upnp listener exiting\n");
pthread_exit(NULL);
}
/**
* process a upnp packet. This is either a discovery or an advertisement
* from another device on the local netowrk.
*/
void upnp_process_packet(void) {
char upnp_packet[UPNP_MAX_PACKET];
ssize_t bytes_read;
char *line_start, *line_end;
int discovery = 0;
int mx = 0;
char *query=NULL;
struct sockaddr_in from;
socklen_t from_len;
UPNP_DISCO *pdisco;
from_len = sizeof(from);
bytes_read = recvfrom(upnp_socket,&upnp_packet,sizeof(upnp_packet),0,
(struct sockaddr *)&from, &from_len);
/* now, parse the packet. If it's a upnp query, it's a
* packet with a ssdp of "discover".
*/
line_start = line_end = upnp_packet;
while((line_end - upnp_packet) < bytes_read) {
while(((*line_start=='\n')||(*line_start=='\r')) &&
(line_end - upnp_packet < bytes_read)) {
/* absorb empty cr/lf pairs */
line_start++;
line_end++;
}
while((*line_end != '\n') && (*line_end != '\r') &&
(line_end - upnp_packet < bytes_read))
line_end++;
if((*line_end == '\n') || (*line_end == '\r')) {
/* we have a full line */
*line_end = '\0';
line_end++;
/* okay... see what it is */
if(strncasecmp(line_start,"M-SEARCH",8) == 0) {
DPRINTF(E_DBG,L_MISC,"upnp_packet: M-SEARCH from %s\n",
inet_ntoa(from.sin_addr));
discovery=1;
}
if(strncasecmp(line_start,"ST:",3)==0) {
/* There is a search term specified */
if(query)
free(query);
line_start += 3;
while(*line_start && *line_start == ' ')
line_start++;
query = strdup(line_start);
DPRINTF(E_DBG,L_MISC,"upnp_packet: Query: %s\n",query);
}
if(strncasecmp(line_start,"MX:",3)==0) {
mx = atoi(line_start + 3);
}
}
line_start=line_end;
/* we could process the packet, and advertise, if necessary */
}
if(discovery) {
DPRINTF(E_DBG,L_MISC,"Responding to query request...\n");
/* 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->query = query;
query = NULL;
memcpy((void*)&pdisco->to, (void*)&from, sizeof(struct sockaddr));
}
if(query) {
free(query);
query = NULL;
}
}
/** /**
* turn off any upnp services. Should really de-register * turn off any upnp services. Should really de-register
* all registered devices, etc. * all registered devices, etc.
*/ */
int upnp_deinit(void) { int upnp_deinit(void) {
close(upnp_socket); void *result_ptr;
upnp_quitflag=1;
upnp_broadcast(UPNP_TYPE_BYEBYE,NULL);
if(upnp_socket)
close(upnp_socket);
/* socket is dead, quitflag is set, listener should terminate
* reasonably quickly */
if(upnp_thread_started) {
upnp_thread_started=0;
pthread_join(upnp_listener_tid, &result_ptr);
}
return TRUE; return TRUE;
} }

View File

@ -33,10 +33,16 @@
#define UPNP_UUID "12345678-1234-1234-1234-123456789013" #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
extern int upnp_init(void); extern int upnp_init(void);
extern int upnp_tick(void); extern int upnp_tick(void);
extern int upnp_deinit(void); extern int upnp_deinit(void);
extern void upnp_add_packet(char *group_id, char *location, extern void upnp_add_packet(char *group_id, int type, char *location,
char *usn, char *nt, char *body); char *namespace, char *name, int version,
char *body);
#endif /* _UPNP_H_ */ #endif /* _UPNP_H_ */