Browse Source

improve SSDP, like not making headers case sensitive... send out

notifications when we register an address...

make it so we send our responses on our own ip, and not the
multicast, along with turning off multicast loopback so we
don't see our own traffic...

Move the registering the SSDP services after the start of the
http server since we now send out notifications...

[git-p4: depot-paths = "//depot/": change = 718]
replace/5b80aeb26dc425aaddcd5182126c969e5cc04cbb
John-Mark Gurney 19 years ago
parent
commit
30b233410d
2 changed files with 94 additions and 55 deletions
  1. +54
    -30
      SSDP.py
  2. +40
    -25
      pymediaserv

+ 54
- 30
SSDP.py View File

@@ -27,7 +27,9 @@ class SSDPServer(DatagramProtocol):
"""A class implementing a SSDP server. The notifyReceived and
searchReceived methods are called when the appropriate type of
datagram is received by the server."""

# not used yet
stdheaders = [ ('Server', 'Twisted, UPnP/1.0, python-upnp'), ]
elements = {}
known = {}

@@ -36,37 +38,40 @@ class SSDPServer(DatagramProtocol):

# Break up message in to command and headers

data = string.replace(data, '\r', '')
data = string.replace(data, ': ', ':')
lines = string.split(data, '\n')
lines = filter(lambda x: len(x) > 0, lines)
log.msg('respond to: %s:%d' % (host, port))
header, payload = data.split('\r\n\r\n')
lines = header.split('\r\n')
cmd = string.split(lines[0], ' ')
lines = map(lambda x: x.replace(': ', ':', 1), lines[1:])
lines = filter(lambda x: len(x) > 0, lines)

headers = dict([string.split(x, ':', 1) for x in lines[1:]])

# TODO: datagram may contain a payload, i.e Content-Length
# header is > 0. Maybe use some twisted HTTP object to do the
# parsing for us.

# SSDP discovery
headers = [string.split(x, ':', 1) for x in lines[1:]]
headers = dict(map(lambda x: (x[0].lower(), x[1]), headers))

if cmd[0] == 'M-SEARCH' and cmd[1] == '*':
# SSDP discovery
self.discoveryRequest(headers, (host, port))

# SSDP presence

if cmd[0] == 'NOTIFY' and cmd[1] == '*':
elif cmd[0] == 'NOTIFY' and cmd[1] == '*':
# SSDP presence
self.notifyReceived(headers, (host, port))
else:
log.msg('Unknown SSDP command %s %s' % cmd)

def discoveryRequest(self, headers, (host, port)):
"""Process a discovery request. The response must be sent to
the address specified by (host, port)."""

log.msg('Discovery request for %s' % headers['ST'])
log.msg('Discovery request for %s' % headers['st'])

# Do we know about this service?

if not self.known.has_key(headers['ST']):
if headers['st'] == 'ssdp:all':
for i in self.known:
hcopy = dict(headers.iteritems())
hcopy['st'] = i
self.discoveryRequest(hcopy, (host, post))
return
if not self.known.has_key(headers['st']):
return

# Generate a response
@@ -74,11 +79,12 @@ class SSDPServer(DatagramProtocol):
response = []
response.append('HTTP/1.1 200 OK')

for k, v in self.known[headers['ST']].items():
for k, v in self.known[headers['st']].items():
response.append('%s: %s' % (k, v))

log.msg('responding with: %s' % response)

# TODO: we should wait random(headers['mx'])
self.transport.write(
string.join(response, '\r\n') + '\r\n\r\n', (host, port))

@@ -95,36 +101,54 @@ class SSDPServer(DatagramProtocol):
self.known[st]['EXT'] = ''
self.known[st]['SERVER'] = 'Twisted, UPnP/1.0, python-upnp'
self.known[st]['CACHE-CONTROL'] = 'max-age=1800'
self.doNotify(st)

def doNotify(self, st):
"""Do notification"""

log.msg('Sending alive notification for %s' % st)

resp = [ 'NOTIFY * HTTP/1.1',
'Host: %s:%d' % (SSDP_ADDR, SSDP_PORT),
'NTS: ssdp:alive',
]
stcpy = dict(self.known[st].iteritems())
stcpy['NT'] = stcpy['ST']
del stcpy['ST']
resp.extend(map(lambda x: ': '.join(x), stcpy.iteritems()))
log.msg(repr(resp))
self.transport.write(
string.join(resp, '\r\n') + '\r\n\r\n', (SSDP_ADDR, SSDP_PORT))

def notifyReceived(self, headers, (host, port)):
"""Process a presence announcement. We just remember the
details of the SSDP service announced."""

if headers['NTS'] == 'ssdp:alive':
if headers['nts'] == 'ssdp:alive':

if not self.elements.has_key(headers['NT']):
if not self.elements.has_key(headers['nt']):

# Register device/service

self.elements[headers['NT']] = {}
self.elements[headers['NT']]['USN'] = headers['USN']
self.elements[headers['NT']]['host'] = (host, port)
self.elements[headers['nt']] = {}
self.elements[headers['nt']]['USN'] = headers['usn']
self.elements[headers['nt']]['host'] = (host, port)

log.msg('Detected presence for %s' % headers['NT'])
log.msg('Detected presence for %s' % headers['nt'])

elif headers['NTS'] == 'ssdp:byebye':
elif headers['nts'] == 'ssdp:byebye':

if self.elements.has_key(headers['NT']):
if self.elements.has_key(headers['nt']):

# Unregister device/service
del(self.elements[headers['NT']])
del(self.elements[headers['nt']])

log.msg('Detected absence for %s' % headers['NT'])
log.msg('Detected absence for %s' % headers['nt'])
else:
log.msg('Unknown subtype %s for notification type %s' %
(headers['NTS'], headers['NT']))
(headers['nts'], headers['nt']))

def findService(self, name):
"""Return information about a service registered over SSDP."""


+ 40
- 25
pymediaserv View File

@@ -1,15 +1,28 @@
#!/usr/bin/python
#!/usr/bin/env python

# Licensed under the MIT license
# http://opensource.org/licenses/mit-license.php

# (c) 2005, Tim Potter <tpot@samba.org>

import random
import string
import sys
from twisted.python import log
from twisted.internet import reactor

def generateuuid():
if False:
return 'asdflkjewoifjslkdfj'
return ''.join(map(lambda x: random.choice(string.letters), xrange(20)))

listenAddr = sys.argv[1]
if len(sys.argv) > 2:
listenPort = int(sys.argv[2])
if listenPort < 1024 or listenPort > 65535:
raise ValueError, 'port out of range'
else:
listenPort = 8080

log.startLogging(sys.stdout)

@@ -19,30 +32,11 @@ from SSDP import SSDPServer, SSDP_PORT, SSDP_ADDR

s = SSDPServer()

port = reactor.listenMulticast(SSDP_PORT, s, SSDP_ADDR)
port.joinGroup(SSDP_ADDR, listenAddr)

uuid = 'uuid:XVKKBUKYRDLGJQDTPOT'

s.register('%s::upnp:rootdevice' % uuid,
'upnp:rootdevice',
'http://%s:8080/root-device.xml' % listenAddr)

s.register(uuid,
uuid,
'http://%s:8080/root-device.xml' % listenAddr)

s.register('%s::urn:schemas-upnp-org:device:MediaServer:1' % uuid,
'urn:schemas-upnp-org:device:MediaServer:1',
'http://%s:8080/root-device.xml' % listenAddr)

s.register('%s::urn:schemas-upnp-org:service:ConnectionManager:1' % uuid,
'urn:schemas-upnp-org:device:ConnectionManager:1',
'http://%s:8080/root-device.xml' % listenAddr)
port = reactor.listenMulticast(SSDP_PORT, s)
port.joinGroup(SSDP_ADDR)
port.setLoopbackMode(0) # don't get our own sends

s.register('%s::urn:schemas-upnp-org:service:ContentDirectory:1' % uuid,
'urn:schemas-upnp-org:device:ContentDirectory:1',
'http://%s:8080/root-device.xml' % listenAddr)
uuid = 'uuid:' + generateuuid()

# Create SOAP server

@@ -70,7 +64,28 @@ from MediaServer import MediaServer
root.putChild('media', static.File('media'))

site = server.Site(root)
reactor.listenTCP(8080, site)
reactor.listenTCP(listenPort, site)

# we need to do this after the children are there, since we send notifies
s.register('%s::upnp:rootdevice' % uuid,
'upnp:rootdevice',
'http://%s:%d/root-device.xml' % (listenAddr, listenPort))

s.register(uuid,
uuid,
'http://%s:%d/root-device.xml' % (listenAddr, listenPort))

s.register('%s::urn:schemas-upnp-org:device:MediaServer:1' % uuid,
'urn:schemas-upnp-org:device:MediaServer:1',
'http://%s:%d/root-device.xml' % (listenAddr, listenPort))

s.register('%s::urn:schemas-upnp-org:service:ConnectionManager:1' % uuid,
'urn:schemas-upnp-org:device:ConnectionManager:1',
'http://%s:%d/root-device.xml' % (listenAddr, listenPort))

s.register('%s::urn:schemas-upnp-org:service:ContentDirectory:1' % uuid,
'urn:schemas-upnp-org:device:ContentDirectory:1',
'http://%s:%d/root-device.xml' % (listenAddr, listenPort))

# Main loop



Loading…
Cancel
Save