mirror of
https://github.com/9001/copyparty.git
synced 2025-08-18 09:22:31 -06:00
add ssdp responder
This commit is contained in:
parent
5a3e504ec4
commit
5cd9d11329
|
@ -20,6 +20,7 @@ import sys
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
|
import uuid
|
||||||
from textwrap import dedent
|
from textwrap import dedent
|
||||||
|
|
||||||
from .__init__ import ANYWIN, CORES, PY2, VT100, WINDOWS, E, EnvParams, unicode
|
from .__init__ import ANYWIN, CORES, PY2, VT100, WINDOWS, E, EnvParams, unicode
|
||||||
|
@ -663,6 +664,11 @@ def run_argparse(
|
||||||
ap2.add_argument("--ssl-log", metavar="PATH", type=u, help="log master secrets for later decryption in wireshark")
|
ap2.add_argument("--ssl-log", metavar="PATH", type=u, help="log master secrets for later decryption in wireshark")
|
||||||
|
|
||||||
ap2 = ap.add_argument_group("Zeroconf options")
|
ap2 = ap.add_argument_group("Zeroconf options")
|
||||||
|
ap2.add_argument("-z", action="store_true", help="enable all zeroconf backends (mdns, ssdp)")
|
||||||
|
ap2.add_argument("-zv", action="store_true", help="verbose all zeroconf backends")
|
||||||
|
ap2.add_argument("--mc-hop", metavar="SEC", type=int, default=0, help="rejoin multicast groups every SEC seconds (workaround for some switches/routers which cause mDNS to suddenly stop working after some time); try [\033[32m300\033[0m] or [\033[32m180\033[0m]")
|
||||||
|
|
||||||
|
ap2 = ap.add_argument_group("Zeroconf-mDNS options:")
|
||||||
ap2.add_argument("--zm", action="store_true", help="announce the enabled protocols over mDNS (multicast DNS-SD) -- compatible with KDE, gnome, macOS, ...")
|
ap2.add_argument("--zm", action="store_true", help="announce the enabled protocols over mDNS (multicast DNS-SD) -- compatible with KDE, gnome, macOS, ...")
|
||||||
ap2.add_argument("--zm4", action="store_true", help="IPv4 only -- try this if some clients can't connect")
|
ap2.add_argument("--zm4", action="store_true", help="IPv4 only -- try this if some clients can't connect")
|
||||||
ap2.add_argument("--zm6", action="store_true", help="IPv6 only")
|
ap2.add_argument("--zm6", action="store_true", help="IPv6 only")
|
||||||
|
@ -676,7 +682,14 @@ def run_argparse(
|
||||||
ap2.add_argument("--zm-mnic", action="store_true", help="merge NICs which share subnets; assume that same subnet means same network")
|
ap2.add_argument("--zm-mnic", action="store_true", help="merge NICs which share subnets; assume that same subnet means same network")
|
||||||
ap2.add_argument("--zm-msub", action="store_true", help="merge subnets on each NIC -- always enabled for ipv6 -- reduces network load, but gnome-gvfs clients may stop working")
|
ap2.add_argument("--zm-msub", action="store_true", help="merge subnets on each NIC -- always enabled for ipv6 -- reduces network load, but gnome-gvfs clients may stop working")
|
||||||
ap2.add_argument("--zm-noneg", action="store_true", help="disable NSEC replies -- try this if some clients don't see copyparty")
|
ap2.add_argument("--zm-noneg", action="store_true", help="disable NSEC replies -- try this if some clients don't see copyparty")
|
||||||
ap2.add_argument("--mc-hop", metavar="SEC", type=int, default=0, help="rejoin multicast groups every SEC seconds (workaround for some switches/routers which cause mDNS to suddenly stop working after some time); try [\033[32m300\033[0m] or [\033[32m180\033[0m]")
|
|
||||||
|
ap2 = ap.add_argument_group("Zeroconf-SSDP options:")
|
||||||
|
ap2.add_argument("--zs", action="store_true", help="announce the enabled protocols over SSDP -- compatible with Windows")
|
||||||
|
# ap2.add_argument("--zs4", action="store_true", help="IPv4 only")
|
||||||
|
# ap2.add_argument("--zs6", action="store_true", help="IPv6 only")
|
||||||
|
ap2.add_argument("--zsv", action="store_true", help="verbose SSDP")
|
||||||
|
ap2.add_argument("--zsl", metavar="PATH", type=u, default="", help="location to include in the url (or a complete external URL), for example [\033[32mpriv/?pw=hunter2\033[0m] or [\033[32mpriv/?pw=hunter2\033[0m]")
|
||||||
|
ap2.add_argument("--zsid", metavar="UUID", type=u, default=uuid.uuid4().urn[4:], help="USN (device identifier) to announce")
|
||||||
|
|
||||||
ap2 = ap.add_argument_group('FTP options')
|
ap2 = ap.add_argument_group('FTP options')
|
||||||
ap2.add_argument("--ftp", metavar="PORT", type=int, help="enable FTP server on PORT, for example \033[32m3921")
|
ap2.add_argument("--ftp", metavar="PORT", type=int, help="enable FTP server on PORT, for example \033[32m3921")
|
||||||
|
|
|
@ -643,6 +643,9 @@ class HttpCli(object):
|
||||||
if self.vpath.startswith(".cpr/ico/"):
|
if self.vpath.startswith(".cpr/ico/"):
|
||||||
return self.tx_ico(self.vpath.split("/")[-1], exact=True)
|
return self.tx_ico(self.vpath.split("/")[-1], exact=True)
|
||||||
|
|
||||||
|
if self.vpath.startswith(".cpr/ssdp"):
|
||||||
|
return self.conn.hsrv.ssdp.reply(self)
|
||||||
|
|
||||||
static_path = os.path.join(self.E.mod, "web/", self.vpath[5:])
|
static_path = os.path.join(self.E.mod, "web/", self.vpath[5:])
|
||||||
return self.tx_file(static_path)
|
return self.tx_file(static_path)
|
||||||
|
|
||||||
|
@ -2626,14 +2629,14 @@ class HttpCli(object):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def set_k304(self) -> bool:
|
def set_k304(self) -> bool:
|
||||||
ck = gencookie("k304", self.uparam["k304"], 60 * 60 * 24 * 365)
|
ck = gencookie("k304", self.uparam["k304"], 60 * 60 * 24 * 299)
|
||||||
self.out_headerlist.append(("Set-Cookie", ck))
|
self.out_headerlist.append(("Set-Cookie", ck))
|
||||||
self.redirect("", "?h#cc")
|
self.redirect("", "?h#cc")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def set_am_js(self) -> bool:
|
def set_am_js(self) -> bool:
|
||||||
v = "n" if self.uparam["am_js"] == "n" else "y"
|
v = "n" if self.uparam["am_js"] == "n" else "y"
|
||||||
ck = gencookie("js", v, 60 * 60 * 24 * 365)
|
ck = gencookie("js", v, 60 * 60 * 24 * 299)
|
||||||
self.out_headerlist.append(("Set-Cookie", ck))
|
self.out_headerlist.append(("Set-Cookie", ck))
|
||||||
self.reply(b"promoted\n")
|
self.reply(b"promoted\n")
|
||||||
return True
|
return True
|
||||||
|
|
|
@ -47,6 +47,7 @@ from .util import (
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .broker_util import BrokerCli
|
from .broker_util import BrokerCli
|
||||||
|
from .ssdp import SSDPr
|
||||||
|
|
||||||
if True: # pylint: disable=using-constant-test
|
if True: # pylint: disable=using-constant-test
|
||||||
from typing import Any, Optional
|
from typing import Any, Optional
|
||||||
|
@ -71,11 +72,14 @@ class HttpSrv(object):
|
||||||
|
|
||||||
nsuf = "-n{}-i{:x}".format(nid, os.getpid()) if nid else ""
|
nsuf = "-n{}-i{:x}".format(nid, os.getpid()) if nid else ""
|
||||||
self.magician = Magician()
|
self.magician = Magician()
|
||||||
|
self.ssdp: Optional["SSDPr"] = None
|
||||||
self.gpwd = Garda(self.args.ban_pw)
|
self.gpwd = Garda(self.args.ban_pw)
|
||||||
self.g404 = Garda(self.args.ban_404)
|
self.g404 = Garda(self.args.ban_404)
|
||||||
self.bans: dict[str, int] = {}
|
self.bans: dict[str, int] = {}
|
||||||
self.aclose: dict[str, int] = {}
|
self.aclose: dict[str, int] = {}
|
||||||
|
|
||||||
|
self.ip = ""
|
||||||
|
self.port = 0
|
||||||
self.name = "hsrv" + nsuf
|
self.name = "hsrv" + nsuf
|
||||||
self.mutex = threading.Lock()
|
self.mutex = threading.Lock()
|
||||||
self.stopping = False
|
self.stopping = False
|
||||||
|
@ -105,6 +109,11 @@ class HttpSrv(object):
|
||||||
zs = os.path.join(self.E.mod, "web", "deps", "prism.js.gz")
|
zs = os.path.join(self.E.mod, "web", "deps", "prism.js.gz")
|
||||||
self.prism = os.path.exists(zs)
|
self.prism = os.path.exists(zs)
|
||||||
|
|
||||||
|
if self.args.zs:
|
||||||
|
from .ssdp import SSDPr
|
||||||
|
|
||||||
|
self.ssdp = SSDPr(broker)
|
||||||
|
|
||||||
cert_path = os.path.join(self.E.cfg, "cert.pem")
|
cert_path = os.path.join(self.E.cfg, "cert.pem")
|
||||||
if bos.path.exists(cert_path):
|
if bos.path.exists(cert_path):
|
||||||
self.cert_path = cert_path
|
self.cert_path = cert_path
|
||||||
|
@ -173,12 +182,12 @@ class HttpSrv(object):
|
||||||
sck.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
sck.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
||||||
sck.settimeout(None) # < does not inherit, ^ does
|
sck.settimeout(None) # < does not inherit, ^ does
|
||||||
|
|
||||||
ip, port = sck.getsockname()[:2]
|
self.ip, self.port = sck.getsockname()[:2]
|
||||||
self.srvs.append(sck)
|
self.srvs.append(sck)
|
||||||
self.nclimax = math.ceil(self.args.nc * 1.0 / nlisteners)
|
self.nclimax = math.ceil(self.args.nc * 1.0 / nlisteners)
|
||||||
Daemon(
|
Daemon(
|
||||||
self.thr_listen,
|
self.thr_listen,
|
||||||
"httpsrv-n{}-listen-{}-{}".format(self.nid or "0", ip, port),
|
"httpsrv-n{}-listen-{}-{}".format(self.nid or "0", self.ip, self.port),
|
||||||
(sck,),
|
(sck,),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -62,11 +62,10 @@ class MDNS(MCast):
|
||||||
def __init__(self, hub: "SvcHub") -> None:
|
def __init__(self, hub: "SvcHub") -> None:
|
||||||
grp4 = "" if hub.args.zm6 else MDNS4
|
grp4 = "" if hub.args.zm6 else MDNS4
|
||||||
grp6 = "" if hub.args.zm4 else MDNS6
|
grp6 = "" if hub.args.zm4 else MDNS6
|
||||||
super(MDNS, self).__init__(hub, MDNS_Sck, grp4, grp6, 5353)
|
super(MDNS, self).__init__(hub, MDNS_Sck, grp4, grp6, 5353, hub.args.zmv)
|
||||||
self.srv: dict[socket.socket, MDNS_Sck] = {}
|
self.srv: dict[socket.socket, MDNS_Sck] = {}
|
||||||
|
|
||||||
self.ttl = 300
|
self.ttl = 300
|
||||||
self.running = True
|
|
||||||
|
|
||||||
zs = self.args.name + ".local."
|
zs = self.args.name + ".local."
|
||||||
zs = zs.encode("ascii", "replace").decode("ascii", "replace")
|
zs = zs.encode("ascii", "replace").decode("ascii", "replace")
|
||||||
|
@ -310,12 +309,12 @@ class MDNS(MCast):
|
||||||
|
|
||||||
self.srv = {}
|
self.srv = {}
|
||||||
|
|
||||||
def eat(self, buf: bytes, addr: tuple[str, int], sck: socket.socket):
|
def eat(self, buf: bytes, addr: tuple[str, int], sck: socket.socket) -> None:
|
||||||
cip = addr[0]
|
cip = addr[0]
|
||||||
if cip.startswith("169.254"):
|
v6 = ":" in cip
|
||||||
|
if cip.startswith("169.254") or v6 and not cip.startswith("fe80"):
|
||||||
return
|
return
|
||||||
|
|
||||||
v6 = ":" in cip
|
|
||||||
cache = self.rx6 if v6 else self.rx4
|
cache = self.rx6 if v6 else self.rx4
|
||||||
if buf in cache.c:
|
if buf in cache.c:
|
||||||
return
|
return
|
||||||
|
@ -327,7 +326,7 @@ class MDNS(MCast):
|
||||||
|
|
||||||
now = time.time()
|
now = time.time()
|
||||||
|
|
||||||
if self.args.zmv:
|
if self.args.zmv and cip != srv.ip and cip not in srv.ips:
|
||||||
t = "{} [{}] \033[36m{} \033[0m|{}|"
|
t = "{} [{}] \033[36m{} \033[0m|{}|"
|
||||||
self.log(t.format(srv.name, srv.ip, cip, len(buf)), "90")
|
self.log(t.format(srv.name, srv.ip, cip, len(buf)), "90")
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ import time
|
||||||
import ipaddress
|
import ipaddress
|
||||||
from ipaddress import IPv4Address, IPv4Network, IPv6Address, IPv6Network
|
from ipaddress import IPv4Address, IPv4Network, IPv6Address, IPv6Network
|
||||||
|
|
||||||
from .__init__ import TYPE_CHECKING, MACOS
|
from .__init__ import MACOS, TYPE_CHECKING
|
||||||
from .util import min_ex, spack
|
from .util import min_ex, spack
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
@ -47,7 +47,13 @@ class MC_Sck(object):
|
||||||
|
|
||||||
class MCast(object):
|
class MCast(object):
|
||||||
def __init__(
|
def __init__(
|
||||||
self, hub: "SvcHub", Srv: type[MC_Sck], mc_grp_4: str, mc_grp_6: str, port: int
|
self,
|
||||||
|
hub: "SvcHub",
|
||||||
|
Srv: type[MC_Sck],
|
||||||
|
mc_grp_4: str,
|
||||||
|
mc_grp_6: str,
|
||||||
|
port: int,
|
||||||
|
vinit: bool,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""disable ipv%d by setting mc_grp_%d empty"""
|
"""disable ipv%d by setting mc_grp_%d empty"""
|
||||||
self.hub = hub
|
self.hub = hub
|
||||||
|
@ -58,6 +64,7 @@ class MCast(object):
|
||||||
self.grp4 = mc_grp_4
|
self.grp4 = mc_grp_4
|
||||||
self.grp6 = mc_grp_6
|
self.grp6 = mc_grp_6
|
||||||
self.port = port
|
self.port = port
|
||||||
|
self.vinit = vinit
|
||||||
|
|
||||||
self.srv: dict[socket.socket, MC_Sck] = {} # listening sockets
|
self.srv: dict[socket.socket, MC_Sck] = {} # listening sockets
|
||||||
self.sips: set[str] = set() # all listening ips (including failed attempts)
|
self.sips: set[str] = set() # all listening ips (including failed attempts)
|
||||||
|
@ -66,6 +73,8 @@ class MCast(object):
|
||||||
self.b6: list[bytes] = [] # sorted list of binary-ips
|
self.b6: list[bytes] = [] # sorted list of binary-ips
|
||||||
self.cscache: dict[str, Optional[MC_Sck]] = {} # client ip -> server cache
|
self.cscache: dict[str, Optional[MC_Sck]] = {} # client ip -> server cache
|
||||||
|
|
||||||
|
self.running = True
|
||||||
|
|
||||||
def log(self, msg: str, c: Union[int, str] = 0) -> None:
|
def log(self, msg: str, c: Union[int, str] = 0) -> None:
|
||||||
self.log_func("multicast", msg, c)
|
self.log_func("multicast", msg, c)
|
||||||
|
|
||||||
|
@ -188,7 +197,7 @@ class MCast(object):
|
||||||
def setup_socket(self, srv: MC_Sck) -> None:
|
def setup_socket(self, srv: MC_Sck) -> None:
|
||||||
sck = srv.sck
|
sck = srv.sck
|
||||||
if srv.v6:
|
if srv.v6:
|
||||||
if self.args.zmv:
|
if self.vinit:
|
||||||
zsl = list(srv.ips.keys())
|
zsl = list(srv.ips.keys())
|
||||||
self.log("v6({}) idx({}) {}".format(srv.ip, srv.idx, zsl), 6)
|
self.log("v6({}) idx({}) {}".format(srv.ip, srv.idx, zsl), 6)
|
||||||
|
|
||||||
|
@ -214,7 +223,7 @@ class MCast(object):
|
||||||
t = "failed to set IPv6 TTL/LOOP; announcements may not survive multiple switches/routers"
|
t = "failed to set IPv6 TTL/LOOP; announcements may not survive multiple switches/routers"
|
||||||
self.log(t, 3)
|
self.log(t, 3)
|
||||||
else:
|
else:
|
||||||
if self.args.zmv:
|
if self.vinit:
|
||||||
self.log("v4({}) idx({})".format(srv.ip, srv.idx), 6)
|
self.log("v4({}) idx({})".format(srv.ip, srv.idx), 6)
|
||||||
|
|
||||||
bip = socket.inet_aton(srv.ip)
|
bip = socket.inet_aton(srv.ip)
|
||||||
|
|
181
copyparty/ssdp.py
Normal file
181
copyparty/ssdp.py
Normal file
|
@ -0,0 +1,181 @@
|
||||||
|
# coding: utf-8
|
||||||
|
from __future__ import print_function, unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
import select
|
||||||
|
import socket
|
||||||
|
from email.utils import formatdate
|
||||||
|
|
||||||
|
from .__init__ import TYPE_CHECKING
|
||||||
|
from .multicast import MC_Sck, MCast
|
||||||
|
from .util import CachedSet, min_ex
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from .broker_util import BrokerCli
|
||||||
|
from .httpcli import HttpCli
|
||||||
|
from .svchub import SvcHub
|
||||||
|
|
||||||
|
if True: # pylint: disable=using-constant-test
|
||||||
|
from typing import Optional, Union
|
||||||
|
|
||||||
|
|
||||||
|
SSDP4 = "239.255.255.250"
|
||||||
|
SSDP6 = "ff02::c"
|
||||||
|
|
||||||
|
|
||||||
|
class SSDPr(object):
|
||||||
|
"""generates http responses for httpcli"""
|
||||||
|
|
||||||
|
def __init__(self, broker: "BrokerCli") -> None:
|
||||||
|
self.broker = broker
|
||||||
|
self.args = broker.args
|
||||||
|
|
||||||
|
def reply(self, hc: "HttpCli") -> bool:
|
||||||
|
if hc.vpath.endswith("device.xml"):
|
||||||
|
return self.tx_device(hc)
|
||||||
|
|
||||||
|
hc.reply(b"unknown request", 400)
|
||||||
|
return False
|
||||||
|
|
||||||
|
def tx_device(self, hc: "HttpCli") -> bool:
|
||||||
|
zs = """
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<root xmlns="urn:schemas-upnp-org:device-1-0">
|
||||||
|
<specVersion>
|
||||||
|
<major>1</major>
|
||||||
|
<minor>0</minor>
|
||||||
|
</specVersion>
|
||||||
|
<URLBase>{}</URLBase>
|
||||||
|
<device>
|
||||||
|
<presentationURL>{}</presentationURL>
|
||||||
|
<deviceType>urn:schemas-upnp-org:device:Basic:1</deviceType>
|
||||||
|
<friendlyName>{}</friendlyName>
|
||||||
|
<modelDescription>file server</modelDescription>
|
||||||
|
<manufacturer>ed</manufacturer>
|
||||||
|
<manufacturerURL>https://ocv.me/</manufacturerURL>
|
||||||
|
<modelName>copyparty</modelName>
|
||||||
|
<modelURL>https://github.com/9001/copyparty/</modelURL>
|
||||||
|
<UDN>{}</UDN>
|
||||||
|
<serviceList>
|
||||||
|
<service>
|
||||||
|
<serviceType>urn:schemas-upnp-org:device:Basic:1</serviceType>
|
||||||
|
<serviceId>urn:schemas-upnp-org:device:Basic</serviceId>
|
||||||
|
<controlURL>/.cpr/ssdp/services.xml</controlURL>
|
||||||
|
<eventSubURL>/.cpr/ssdp/services.xml</eventSubURL>
|
||||||
|
<SCPDURL>/.cpr/ssdp/services.xml</SCPDURL>
|
||||||
|
</service>
|
||||||
|
</serviceList>
|
||||||
|
</device>
|
||||||
|
</root>"""
|
||||||
|
|
||||||
|
sip, sport = hc.s.getsockname()[:2]
|
||||||
|
proto = "https" if self.args.https_only else "http"
|
||||||
|
ubase = "{}://{}:{}".format(proto, sip, sport)
|
||||||
|
zsl = self.args.zsl
|
||||||
|
url = zsl if "://" in zsl else ubase + "/" + zsl.lstrip("/")
|
||||||
|
zs = zs.strip().format(ubase, url, self.args.name, self.args.zsid)
|
||||||
|
hc.reply(zs.encode("utf-8", "replace"))
|
||||||
|
return False # close connectino
|
||||||
|
|
||||||
|
|
||||||
|
class SSDPd(MCast):
|
||||||
|
"""communicates with ssdp clients over multicast"""
|
||||||
|
|
||||||
|
def __init__(self, hub: "SvcHub") -> None:
|
||||||
|
# grp4 = "" if hub.args.zs6 else SSDP4
|
||||||
|
# grp6 = "" if hub.args.zs4 else SSDP6
|
||||||
|
# no way to find routable IPv6 between us and them
|
||||||
|
grp4 = SSDP4
|
||||||
|
grp6 = ""
|
||||||
|
vinit = hub.args.zsv and not hub.args.zmv
|
||||||
|
super(SSDPd, self).__init__(hub, MC_Sck, grp4, grp6, 1900, vinit)
|
||||||
|
self.srv: dict[socket.socket, MC_Sck] = {}
|
||||||
|
self.rx4 = CachedSet(0.7)
|
||||||
|
self.rx6 = CachedSet(0.7)
|
||||||
|
self.txc = CachedSet(5) # win10: every 3 sec
|
||||||
|
self.ptn_st = re.compile(b"\nst: *upnp:rootdevice", re.I)
|
||||||
|
|
||||||
|
def log(self, msg: str, c: Union[int, str] = 0) -> None:
|
||||||
|
self.log_func("SSDP", msg, c)
|
||||||
|
|
||||||
|
def run(self) -> None:
|
||||||
|
bound = self.create_servers()
|
||||||
|
if not bound:
|
||||||
|
self.log("failed to announce copyparty services on the network", 3)
|
||||||
|
return
|
||||||
|
|
||||||
|
self.log("listening")
|
||||||
|
while self.running:
|
||||||
|
rdy = select.select(self.srv, [], [], 180)
|
||||||
|
rx: list[socket.socket] = rdy[0] # type: ignore
|
||||||
|
self.rx4.cln()
|
||||||
|
self.rx6.cln()
|
||||||
|
for sck in rx:
|
||||||
|
buf, addr = sck.recvfrom(4096)
|
||||||
|
try:
|
||||||
|
self.eat(buf, addr, sck)
|
||||||
|
except:
|
||||||
|
if not self.running:
|
||||||
|
return
|
||||||
|
|
||||||
|
t = "{} {} \033[33m|{}| {}\n{}".format(
|
||||||
|
self.srv[sck].name, addr, len(buf), repr(buf)[2:-1], min_ex()
|
||||||
|
)
|
||||||
|
self.log(t, 6)
|
||||||
|
|
||||||
|
def stop(self) -> None:
|
||||||
|
self.running = False
|
||||||
|
self.srv = {}
|
||||||
|
|
||||||
|
def eat(self, buf: bytes, addr: tuple[str, int], sck: socket.socket) -> None:
|
||||||
|
cip = addr[0]
|
||||||
|
v6 = ":" in cip
|
||||||
|
if cip.startswith("169.254") or v6 and not cip.startswith("fe80"):
|
||||||
|
return
|
||||||
|
|
||||||
|
cache = self.rx6 if v6 else self.rx4
|
||||||
|
if buf in cache.c:
|
||||||
|
return
|
||||||
|
|
||||||
|
cache.add(buf)
|
||||||
|
srv: Optional[MC_Sck] = self.srv[sck] if v6 else self.map_client(cip) # type: ignore
|
||||||
|
if not srv:
|
||||||
|
return
|
||||||
|
|
||||||
|
if not buf.startswith(b"M-SEARCH * HTTP/1."):
|
||||||
|
raise Exception("not an ssdp message")
|
||||||
|
|
||||||
|
if not self.ptn_st.search(buf):
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.args.zsv:
|
||||||
|
t = "{} [{}] \033[36m{} \033[0m|{}|"
|
||||||
|
self.log(t.format(srv.name, srv.ip, cip, len(buf)), "90")
|
||||||
|
|
||||||
|
sip = "[{}]".format(srv.ip) if v6 else srv.ip
|
||||||
|
sport = self.args.p[0] # xxx
|
||||||
|
|
||||||
|
zs = """
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
CACHE-CONTROL: max-age=1800
|
||||||
|
DATE: {0}
|
||||||
|
EXT:
|
||||||
|
LOCATION: http://{1}:{2}/.cpr/ssdp/device.xml
|
||||||
|
OPT: "http://schemas.upnp.org/upnp/1/0/"; ns=01
|
||||||
|
01-NLS: {3}
|
||||||
|
SERVER: UPnP/1.0
|
||||||
|
ST: upnp:rootdevice
|
||||||
|
USN: {3}::upnp:rootdevice
|
||||||
|
BOOTID.UPNP.ORG: 0
|
||||||
|
CONFIGID.UPNP.ORG: 1
|
||||||
|
|
||||||
|
"""
|
||||||
|
zs = zs.format(formatdate(usegmt=True), sip, sport, self.args.zsid)
|
||||||
|
zb = zs[1:].replace("\n", "\r\n").encode("utf-8", "replace")
|
||||||
|
srv.sck.sendto(zb, addr[:2])
|
||||||
|
|
||||||
|
if cip not in self.txc.c:
|
||||||
|
self.log("{} [{}] --> {}".format(srv.name, srv.ip, cip), "6")
|
||||||
|
|
||||||
|
self.txc.add(cip)
|
||||||
|
self.txc.cln()
|
|
@ -1,9 +1,6 @@
|
||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
from __future__ import print_function, unicode_literals
|
from __future__ import print_function, unicode_literals
|
||||||
|
|
||||||
# from inspect import currentframe
|
|
||||||
# print(currentframe().f_lineno)
|
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import base64
|
import base64
|
||||||
import calendar
|
import calendar
|
||||||
|
@ -20,6 +17,10 @@ import threading
|
||||||
import time
|
import time
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
# from inspect import currentframe
|
||||||
|
# print(currentframe().f_lineno)
|
||||||
|
|
||||||
|
|
||||||
if True: # pylint: disable=using-constant-test
|
if True: # pylint: disable=using-constant-test
|
||||||
from types import FrameType
|
from types import FrameType
|
||||||
|
|
||||||
|
@ -49,6 +50,7 @@ from .util import (
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
try:
|
try:
|
||||||
from .mdns import MDNS
|
from .mdns import MDNS
|
||||||
|
from .ssdp import SSDPd
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -235,6 +237,7 @@ class SvcHub(object):
|
||||||
args.zms = zms
|
args.zms = zms
|
||||||
|
|
||||||
self.mdns: Optional["MDNS"] = None
|
self.mdns: Optional["MDNS"] = None
|
||||||
|
self.ssdp: Optional["SSDPd"] = None
|
||||||
|
|
||||||
# decide which worker impl to use
|
# decide which worker impl to use
|
||||||
if self.check_mp_enable():
|
if self.check_mp_enable():
|
||||||
|
@ -395,6 +398,15 @@ class SvcHub(object):
|
||||||
except:
|
except:
|
||||||
self.log("root", "mdns startup failed;\n" + min_ex(), 3)
|
self.log("root", "mdns startup failed;\n" + min_ex(), 3)
|
||||||
|
|
||||||
|
if getattr(self.args, "zs", False):
|
||||||
|
try:
|
||||||
|
from .ssdp import SSDPd
|
||||||
|
|
||||||
|
self.ssdp = SSDPd(self)
|
||||||
|
Daemon(self.ssdp.run, "ssdp")
|
||||||
|
except:
|
||||||
|
self.log("root", "ssdp startup failed;\n" + min_ex(), 3)
|
||||||
|
|
||||||
Daemon(self.thr_httpsrv_up, "sig-hsrv-up2")
|
Daemon(self.thr_httpsrv_up, "sig-hsrv-up2")
|
||||||
|
|
||||||
sigs = [signal.SIGINT, signal.SIGTERM]
|
sigs = [signal.SIGINT, signal.SIGTERM]
|
||||||
|
@ -501,10 +513,15 @@ class SvcHub(object):
|
||||||
try:
|
try:
|
||||||
self.pr("OPYTHAT")
|
self.pr("OPYTHAT")
|
||||||
slp = 0.0
|
slp = 0.0
|
||||||
|
|
||||||
if self.mdns:
|
if self.mdns:
|
||||||
Daemon(self.mdns.stop)
|
Daemon(self.mdns.stop)
|
||||||
slp = time.time() + 0.5
|
slp = time.time() + 0.5
|
||||||
|
|
||||||
|
if self.ssdp:
|
||||||
|
Daemon(self.ssdp.stop)
|
||||||
|
slp = time.time() + 0.5
|
||||||
|
|
||||||
self.tcpsrv.shutdown()
|
self.tcpsrv.shutdown()
|
||||||
self.broker.shutdown()
|
self.broker.shutdown()
|
||||||
self.up2k.shutdown()
|
self.up2k.shutdown()
|
||||||
|
|
|
@ -181,9 +181,14 @@ IMPLICATIONS = [
|
||||||
["smbvvv", "smbvv"],
|
["smbvvv", "smbvv"],
|
||||||
["smbvv", "smbv"],
|
["smbvv", "smbv"],
|
||||||
["smbv", "smb"],
|
["smbv", "smb"],
|
||||||
|
["zv", "zmv"],
|
||||||
|
["zv", "zsv"],
|
||||||
|
["z", "zm"],
|
||||||
|
["z", "zs"],
|
||||||
["zmvv", "zmv"],
|
["zmvv", "zmv"],
|
||||||
["zmv", "zm"],
|
["zmv", "zm"],
|
||||||
["zms", "zm"],
|
["zms", "zm"],
|
||||||
|
["zsv", "zs"],
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue