148 lines
4.4 KiB
Python
148 lines
4.4 KiB
Python
import socket
|
|
import struct
|
|
import threading
|
|
import queue
|
|
import time
|
|
import datetime
|
|
import uuid
|
|
import random
|
|
|
|
class ssdp_response():
|
|
|
|
SSDP_GROUP = '239.255.255.250'
|
|
SSDP_PORT = 1900
|
|
|
|
|
|
TEMPLATE ="""HTTP/1.1 200 OK
|
|
CACHE-CONTROL: max-age={}
|
|
DATE: {}
|
|
EXT:
|
|
LOCATION: {}
|
|
SERVER: {}
|
|
ST: {}
|
|
USN: {}
|
|
BOOTID.UPNP.ORG: {}
|
|
|
|
"""
|
|
MAX_AGE = 500
|
|
LOCATION = ""
|
|
SERVER = "PYTHON/3 UPnP/1.1 CONTROL_SERVER/1.0.0"
|
|
USN = uuid.uuid4()
|
|
BOOTID = 0
|
|
|
|
|
|
queues = {}
|
|
queues_lock = threading.Lock()
|
|
|
|
requests = queue.Queue()
|
|
|
|
filters = {"HEAD":"M-SEARCH"}
|
|
|
|
def __init__(self, filter, location=None):
|
|
self.filters.update(filter)
|
|
if not location:
|
|
self.LOCATION = socket.gethostbyname_ex(socket.getfqdn())[2]
|
|
else:
|
|
self.LOCATION = location
|
|
|
|
def start_listener(self):
|
|
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
|
|
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
sock.bind(("", self.SSDP_PORT))
|
|
|
|
multicast_request = struct.pack("4sl", socket.inet_aton(self.SSDP_GROUP), socket.INADDR_ANY)
|
|
sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, multicast_request)
|
|
#start listen and respond thread
|
|
|
|
while True:
|
|
try:
|
|
data,addr = sock.recvfrom(1024)
|
|
if addr in self.queues:
|
|
self.queues[addr].put(data)
|
|
else:
|
|
q = queue.Queue()
|
|
q.put(data)
|
|
self.queues_lock.acquire()
|
|
self.queues[addr] = q
|
|
self.queues_lock.release()
|
|
except Exception as e:
|
|
print(e)
|
|
print("recv error")
|
|
|
|
def filter(self, request):
|
|
ret = True
|
|
for k in self.filters.keys():
|
|
val = request.get(k)
|
|
if not val:
|
|
ret = False
|
|
break
|
|
else:
|
|
if self.filters[k] not in val:
|
|
ret = False
|
|
break
|
|
return ret
|
|
|
|
def parse_request(self):
|
|
requests_buffer = {}
|
|
while True:
|
|
time.sleep(1)
|
|
self.queues_lock.acquire()
|
|
timeouts = []
|
|
for k, v in self.queues.items():
|
|
try:
|
|
data = v.get(timeout=5)
|
|
string = data.decode()
|
|
lines = string.splitlines()
|
|
for line in lines:
|
|
val = line.split(':', 1)
|
|
if len(val) == 1:
|
|
if "M-SEARCH" in val[0]:
|
|
requests_buffer[k] = {}
|
|
requests_buffer[k]["HEAD"] = val[0]
|
|
else:
|
|
requests_buffer[k]["CLIENT"] = k[0]
|
|
if self.filter(requests_buffer[k]):
|
|
print(requests_buffer[k])
|
|
self.requests.put(requests_buffer.pop(k))
|
|
else:
|
|
requests_buffer[k][val[0]] = val[1]
|
|
except Exception as e:
|
|
print(e)
|
|
print("no data")
|
|
timeouts.append(k)
|
|
for k in timeouts:
|
|
del self.queues[k]
|
|
self.queues_lock.release()
|
|
|
|
def construct_response(self, request):
|
|
response = self.TEMPLATE.format(
|
|
self.MAX_AGE,
|
|
datetime.datetime.now().replace(microsecond=0).isoformat(),
|
|
self.LOCATION,
|
|
self.SERVER,
|
|
request.get("ST"),
|
|
self.USN,
|
|
self.BOOTID)
|
|
self.BOOTID = self.BOOTID + 1
|
|
return response
|
|
|
|
def send_response(self):
|
|
while True:
|
|
request = self.requests.get()
|
|
time.sleep(random.randrange(int(request.get("MX"))))
|
|
response = self.construct_response(request)
|
|
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
|
|
address = (request.get("CLIENT"), self.SSDP_PORT)
|
|
print(response)
|
|
sock.connect(address)
|
|
sock.sendall(response.encode())
|
|
sock.close();
|
|
|
|
|
|
s = ssdp_response({"ST":"service:control-server"}, "192.168.0.184")
|
|
t = threading.Thread(target=s.start_listener)
|
|
t.start()
|
|
t2 = threading.Thread(target=s.parse_request)
|
|
t2.start()
|
|
s.send_response()
|