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 """A class implementing a SSDP server. The notifyReceived and
searchReceived methods are called when the appropriate type of searchReceived methods are called when the appropriate type of
datagram is received by the server.""" datagram is received by the server."""

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


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


# Break up message in to command and headers # 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], ' ') 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] == '*': if cmd[0] == 'M-SEARCH' and cmd[1] == '*':
# SSDP discovery
self.discoveryRequest(headers, (host, port)) 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)) self.notifyReceived(headers, (host, port))
else:
log.msg('Unknown SSDP command %s %s' % cmd)


def discoveryRequest(self, headers, (host, port)): def discoveryRequest(self, headers, (host, port)):
"""Process a discovery request. The response must be sent to """Process a discovery request. The response must be sent to
the address specified by (host, port).""" 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? # 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 return


# Generate a response # Generate a response
@@ -74,11 +79,12 @@ class SSDPServer(DatagramProtocol):
response = [] response = []
response.append('HTTP/1.1 200 OK') 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)) response.append('%s: %s' % (k, v))


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


# TODO: we should wait random(headers['mx'])
self.transport.write( self.transport.write(
string.join(response, '\r\n') + '\r\n\r\n', (host, port)) 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]['EXT'] = ''
self.known[st]['SERVER'] = 'Twisted, UPnP/1.0, python-upnp' self.known[st]['SERVER'] = 'Twisted, UPnP/1.0, python-upnp'
self.known[st]['CACHE-CONTROL'] = 'max-age=1800' 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)): def notifyReceived(self, headers, (host, port)):
"""Process a presence announcement. We just remember the """Process a presence announcement. We just remember the
details of the SSDP service announced.""" 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 # 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 # 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: else:
log.msg('Unknown subtype %s for notification type %s' % log.msg('Unknown subtype %s for notification type %s' %
(headers['NTS'], headers['NT']))
(headers['nts'], headers['nt']))


def findService(self, name): def findService(self, name):
"""Return information about a service registered over SSDP.""" """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 # Licensed under the MIT license
# http://opensource.org/licenses/mit-license.php # http://opensource.org/licenses/mit-license.php


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


import random
import string
import sys import sys
from twisted.python import log from twisted.python import log
from twisted.internet import reactor 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] 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) log.startLogging(sys.stdout)


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


s = SSDPServer() 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 # Create SOAP server


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


site = server.Site(root) 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 # Main loop




Loading…
Cancel
Save