Created
April 20, 2012 12:45
-
-
Save schlamar/2428250 to your computer and use it in GitHub Desktop.
Simple Python SSDP discovery
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import socket | |
import struct | |
import sys | |
from httplib import HTTPResponse | |
from BaseHTTPServer import BaseHTTPRequestHandler | |
from StringIO import StringIO | |
import gtk | |
import gobject | |
LIB_ID = 'my_library' | |
MCAST_GRP = '239.255.255.250' | |
MCAST_PORT = 1900 | |
SERVICE_LOCS = {'id1': '127.0.0.1:7766', 'id2': '127.0.0.1:7766'} | |
DISCOVERY_MSG = ('M-SEARCH * HTTP/1.1\r\n' + | |
'ST: %(library)s:%(service)s\r\n' + | |
'MX: 3\r\n' + | |
'MAN: "ssdp:discover"\r\n' + | |
'HOST: 239.255.255.250:1900\r\n\r\n') | |
LOCATION_MSG = ('HTTP/1.1 200 OK\r\n' + | |
'ST: %(library)s:%(service)s\r\n' | |
'USN: %(service)s\r\n' | |
'Location: %(loc)s\r\n' | |
'Cache-Control: max-age=900\r\n\r\n') | |
class Request(BaseHTTPRequestHandler): | |
def __init__(self, request_text): | |
self.rfile = StringIO(request_text) | |
self.raw_requestline = self.rfile.readline() | |
self.error_code = self.error_message = None | |
self.parse_request() | |
def send_error(self, code, message): | |
self.error_code = code | |
self.error_message = message | |
class Response(HTTPResponse): | |
def __init__(self, response_text): | |
self.fp = StringIO(response_text) | |
self.debuglevel = 0 | |
self.strict = 0 | |
self.msg = None | |
self._method = None | |
self.begin() | |
def interface_addresses(family=socket.AF_INET): | |
for fam, _, _, _, sockaddr in socket.getaddrinfo('', None): | |
if family == fam: | |
yield sockaddr[0] | |
def client(timeout=1, retries=5): | |
socket.setdefaulttimeout(timeout) | |
for _ in xrange(retries): | |
for addr in interface_addresses(): | |
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) | |
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) | |
sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 2) | |
sock.bind((addr, 0)) | |
msg = DISCOVERY_MSG % dict(service='id1', library=LIB_ID) | |
for _ in xrange(2): | |
# sending it more than once will | |
# decrease the probability of a timeout | |
sock.sendto(msg, (MCAST_GRP, MCAST_PORT)) | |
try: | |
data = sock.recv(1024) | |
except socket.timeout: | |
pass | |
else: | |
response = Response(data) | |
print response.getheader('Location') | |
return | |
def server(timeout=5): | |
socket.setdefaulttimeout(timeout) | |
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) | |
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) | |
sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 2) | |
sock.bind(('', MCAST_PORT)) | |
mreq = struct.pack('4sl', socket.inet_aton(MCAST_GRP), socket.INADDR_ANY) | |
sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq) | |
cond = gobject.IO_IN | gobject.IO_HUP | |
gobject.io_add_watch(sock, cond, handle_requests) | |
gtk.main() | |
def handle_requests(sock, _): | |
data, addr = sock.recvfrom(4096) | |
request = Request(data) | |
if not request.error_code and \ | |
request.command == 'M-SEARCH' and \ | |
request.path == '*' and \ | |
request.headers['ST'].startswith(LIB_ID) and \ | |
request.headers['MAN'] == '"ssdp:discover"': | |
service = request.headers['ST'].split(':', 2)[1] | |
if service in SERVICE_LOCS: | |
loc = SERVICE_LOCS[service] | |
msg = LOCATION_MSG % dict(service=service, loc=loc, library=LIB_ID) | |
sock.sendto(msg, addr) | |
return True | |
if __name__ == '__main__': | |
if len(sys.argv) > 1 and 'client' in sys.argv[1]: | |
client() | |
else: | |
server() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I'm trying to adapt this code to write a simple SSDP client and I've run into a couple of issues.
The line
socket.getaddrinfo('', None)
throws the exceptionsocket.gaierror: [Errno 8] nodename nor servname provided, or not known
. It looks likegetaddrinfo
isn't happy with passing in a blank string as the hostname.So I changed it to
socket.getaddrinfo('localhost', None)
and it happily returns "127.0.0.1" which allows the client to carry on.The next issue I run into is this statement
sock.sendto(msg, (MCAST_GRP, MCAST_PORT))
throws an exceptionsocket.error: [Errno 51] Network is unreachable
and this has me totally lost.