* Add the ability to parse the root description of clients after they send an SSDP NOTIFY.

* Using this new capability, detect Roku SoundBridge clients.
This commit is contained in:
Justin Maggard 2011-04-09 01:37:11 +00:00
parent e32a1b1d27
commit 07c5fd5dd6
4 changed files with 208 additions and 15 deletions

View File

@ -68,6 +68,7 @@ enum client_types {
EMediaRoom, EMediaRoom,
ESonyBDP, ESonyBDP,
ESonyBravia, ESonyBravia,
ERokuSoundBridge,
EStandardDLNA150 = 100 EStandardDLNA150 = 100
}; };

View File

@ -41,6 +41,8 @@
#include "minidlnapath.h" #include "minidlnapath.h"
#include "upnphttp.h" #include "upnphttp.h"
#include "upnpglobalvars.h" #include "upnpglobalvars.h"
#include "upnpreplyparse.h"
#include "getifaddr.h"
#include "minissdp.h" #include "minissdp.h"
#include "log.h" #include "log.h"
@ -332,6 +334,137 @@ SendSSDPNotifies2(int * sockets,
} }
} }
void
ParseUPnPClient(char *location)
{
char buf[8192];
struct sockaddr_in dest;
int s, n, do_headers = 0, nread = 0;
struct timeval tv;
char *addr, *path, *port_str;
long port = 80;
char *off = NULL, *p;
int content_len = sizeof(buf);
struct NameValueParserData xml;
int client;
enum client_types type = -1;
uint32_t flags = 0;
char *model;
if (strncmp(location, "http://", 7) != 0)
return;
path = location + 7;
port_str = strsep(&path, "/");
if (!path)
return;
addr = strsep(&port_str, ":");
if (port_str)
{
port = strtol(port_str, NULL, 10);
if (!port)
port = 80;
}
memset(&dest, '\0', sizeof(dest));
if (!inet_aton(addr, &dest.sin_addr))
return;
/* Check if the client is already in cache */
dest.sin_family = AF_INET;
dest.sin_port = htons(port);
s = socket(PF_INET, SOCK_STREAM, 0);
if( s < 0 )
return;
tv.tv_sec = 0;
tv.tv_usec = 500000;
setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
if( connect(s, (struct sockaddr*)&dest, sizeof(struct sockaddr_in)) < 0 )
goto close;
n = snprintf(buf, sizeof(buf), "GET /%s HTTP/1.0\r\n"
"HOST: %s:%ld\r\n\r\n",
path, addr, port);
if( write(s, buf, n) < 1 )
goto close;
while( (n = read(s, buf+nread, sizeof(buf)-nread-1)) > 0 )
{
nread += n;
buf[nread] = '\0';
n = nread;
p = buf;
while( !off && n-- > 0 )
{
if(p[0]=='\r' && p[1]=='\n' && p[2]=='\r' && p[3]=='\n')
{
off = p + 4;
do_headers = 1;
}
p++;
}
if( !off )
continue;
if( do_headers )
{
p = buf;
if( strncmp(p, "HTTP/", 5) != 0 )
goto close;
while(*p != ' ' && *p != '\t') p++;
/* If we don't get a 200 status, ignore it */
if( strtol(p, NULL, 10) != 200 )
goto close;
if( (p = strcasestr(p, "Content-Length:")) )
content_len = strtol(p+15, NULL, 10);
do_headers = 0;
}
if( buf + nread - off >= content_len )
break;
}
close:
close(s);
if( !off )
return;
nread -= off - buf;
ParseNameValue(off, nread, &xml);
model = GetValueFromNameValueList(&xml, "modelName");
if( model )
{
if (strstr(model, "Roku SoundBridge"))
{
type = ERokuSoundBridge;
flags |= FLAG_AUDIO_ONLY;
}
}
if( type < 0 )
return;
client = SearchClientCache(dest.sin_addr, 1);
/* Add this client to the cache if it's not there already. */
if( client < 0 )
{
for( client=0; client<CLIENT_CACHE_SLOTS; client++ )
{
if( clients[client].addr.s_addr )
continue;
get_remote_mac(dest.sin_addr, clients[client].mac);
clients[client].addr = dest.sin_addr;
DPRINTF(E_DEBUG, L_HTTP, "Added client [%d/%s/%02X:%02X:%02X:%02X:%02X:%02X] to cache slot %d.\n",
type, inet_ntoa(clients[client].addr),
clients[client].mac[0], clients[client].mac[1], clients[client].mac[2],
clients[client].mac[3], clients[client].mac[4], clients[client].mac[5], client);
break;
}
}
clients[client].type = type;
clients[client].flags = flags;
clients[client].age = time(NULL);
}
/* ProcessSSDPRequest() /* ProcessSSDPRequest()
* process SSDP M-SEARCH requests and responds to them */ * process SSDP M-SEARCH requests and responds to them */
void void
@ -345,26 +478,22 @@ ProcessSSDPRequest(int s, unsigned short port)
struct sockaddr_in sendername; struct sockaddr_in sendername;
int i, l; int i, l;
int lan_addr_index = 0; int lan_addr_index = 0;
char * st = NULL, * mx = NULL, * man = NULL, * mx_end = NULL; char *st = NULL, *mx = NULL, *man = NULL, *mx_end = NULL, *loc = NULL, *srv = NULL;
int st_len = 0, mx_len = 0, man_len = 0, mx_val = 0; int st_len = 0, mx_len = 0, man_len = 0, mx_val = 0, loc_len = 0, srv_len = 0;
len_r = sizeof(struct sockaddr_in); len_r = sizeof(struct sockaddr_in);
n = recvfrom(s, bufr, sizeof(bufr), 0, n = recvfrom(s, bufr, sizeof(bufr)-1, 0,
(struct sockaddr *)&sendername, &len_r); (struct sockaddr *)&sendername, &len_r);
if(n < 0) if(n < 0)
{ {
DPRINTF(E_ERROR, L_SSDP, "recvfrom(udp): %s\n", strerror(errno)); DPRINTF(E_ERROR, L_SSDP, "recvfrom(udp): %s\n", strerror(errno));
return; return;
} }
bufr[n] = '\0';
if(memcmp(bufr, "NOTIFY", 6) == 0) if(memcmp(bufr, "NOTIFY", 6) == 0)
{ {
/* ignore NOTIFY packets. We could log the sender and device type */ //DEBUG DPRINTF(E_DEBUG, L_SSDP, "Received SSDP notify:\n%.*s", n, bufr);
return;
}
else if(memcmp(bufr, "M-SEARCH", 8) == 0)
{
//DEBUG DPRINTF(E_DEBUG, L_SSDP, "Received SSDP request:\n%.*s", n, bufr);
for(i=0; i < n; i++) for(i=0; i < n; i++)
{ {
if( bufr[i] == '*' ) if( bufr[i] == '*' )
@ -376,10 +505,67 @@ ProcessSSDPRequest(int s, unsigned short port)
} }
while(i < n) while(i < n)
{ {
while((i < n - 1) && (bufr[i] != '\r' || bufr[i+1] != '\n')) while((i < n - 2) && (bufr[i] != '\r' || bufr[i+1] != '\n'))
i++; i++;
i += 2; i += 2;
if((i < n - 3) && (strncasecmp(bufr+i, "ST:", 3) == 0)) if(strncasecmp(bufr+i, "SERVER:", 7) == 0)
{
srv = bufr+i+7;
srv_len = 0;
while(*srv == ' ' || *srv == '\t') srv++;
while(srv[srv_len]!='\r' && srv[srv_len]!='\n') srv_len++;
}
else if(strncasecmp(bufr+i, "LOCATION:", 9) == 0)
{
loc = bufr+i+9;
loc_len = 0;
while(*loc == ' ' || *loc == '\t') loc++;
while(loc[loc_len]!='\r' && loc[loc_len]!='\n') loc_len++;
}
else if(strncasecmp(bufr+i, "NTS:", 4) == 0)
{
man = bufr+i+4;
man_len = 0;
while(*man == ' ' || *man == '\t') man++;
while(man[man_len]!='\r' && man[man_len]!='\n') man_len++;
}
}
if (!man || (strncmp(man, "ssdp:alive", man_len) != 0))
{
return;
}
loc[loc_len] = '\0';
if (strncmp(srv, "Allegro-Software-RomPlug", 24) == 0)
{
/* Check if the client is already in cache */
i = SearchClientCache(sendername.sin_addr, 1);
if( i >= 0 && clients[i].type < EStandardDLNA150 )
{
clients[i].age = time(NULL);
return;
}
ParseUPnPClient(loc);
}
return;
}
else if(memcmp(bufr, "M-SEARCH", 8) == 0)
{
//DPRINTF(E_DEBUG, L_SSDP, "Received SSDP request:\n%.*s", n, bufr);
for(i=0; i < n; i++)
{
if( bufr[i] == '*' )
break;
}
if( !strcasestr(bufr+i, "HTTP/1.1") )
{
return;
}
while(i < n)
{
while((i < n - 2) && (bufr[i] != '\r' || bufr[i+1] != '\n'))
i++;
i += 2;
if(strncasecmp(bufr+i, "ST:", 3) == 0)
{ {
st = bufr+i+3; st = bufr+i+3;
st_len = 0; st_len = 0;

View File

@ -126,7 +126,7 @@ Delete_upnphttp(struct upnphttp * h)
} }
int int
SearchClientCache(struct in_addr addr) SearchClientCache(struct in_addr addr, int quiet)
{ {
int i; int i;
for( i=0; i<CLIENT_CACHE_SLOTS; i++ ) for( i=0; i<CLIENT_CACHE_SLOTS; i++ )
@ -150,7 +150,9 @@ SearchClientCache(struct in_addr addr)
return -1; return -1;
} }
} }
DPRINTF(E_DEBUG, L_HTTP, "Client found in cache. [type %d/entry %d]\n", clients[i].type, i); if( !quiet )
DPRINTF(E_DEBUG, L_HTTP, "Client found in cache. [type %d/entry %d]\n",
clients[i].type, i);
return i; return i;
} }
} }
@ -437,7 +439,7 @@ next_header:
/* If the client type wasn't found, search the cache. /* If the client type wasn't found, search the cache.
* This is done because a lot of clients like to send a * This is done because a lot of clients like to send a
* different User-Agent with different types of requests. */ * different User-Agent with different types of requests. */
n = SearchClientCache(h->clientaddr); n = SearchClientCache(h->clientaddr, 0);
if( h->req_client ) if( h->req_client )
{ {
/* Add this client to the cache if it's not there already. */ /* Add this client to the cache if it's not there already. */
@ -456,7 +458,7 @@ next_header:
break; break;
} }
} }
else if( (n < EStandardDLNA150) && (h->req_client == EStandardDLNA150) ) else if( (clients[n].type < EStandardDLNA150) && (h->req_client == EStandardDLNA150) )
{ {
/* If we know the client and our new detection is generic, use our cached info */ /* If we know the client and our new detection is generic, use our cached info */
h->reqflags |= clients[n].flags; h->reqflags |= clients[n].flags;

View File

@ -110,6 +110,7 @@ struct upnphttp {
#define FLAG_MIME_FLAC_FLAC 0x00800000 #define FLAG_MIME_FLAC_FLAC 0x00800000
#define FLAG_NO_RESIZE 0x01000000 #define FLAG_NO_RESIZE 0x01000000
#define FLAG_MS_PFS 0x02000000 // Microsoft PlaysForSure client #define FLAG_MS_PFS 0x02000000 // Microsoft PlaysForSure client
#define FLAG_AUDIO_ONLY 0x04000000
/* New_upnphttp() */ /* New_upnphttp() */
struct upnphttp * struct upnphttp *
@ -156,6 +157,9 @@ Send501(struct upnphttp *);
void void
SendResp_upnphttp(struct upnphttp *); SendResp_upnphttp(struct upnphttp *);
int
SearchClientCache(struct in_addr addr, int quiet);
void void
SendResp_icon(struct upnphttp *, char * url); SendResp_icon(struct upnphttp *, char * url);
void void