fix mdns on windows

This commit is contained in:
ed 2022-12-03 13:31:00 +00:00
parent 2fbdc0a85e
commit 99efc290df
12 changed files with 90 additions and 198 deletions

View file

@ -12,11 +12,11 @@ except:
TYPE_CHECKING = False
if True:
from typing import Any, Callable, Union
from typing import Any, Callable
PY2 = sys.version_info < (3,)
if not PY2:
unicode: Callable[[Union[str, int, float]], str] = str
unicode: Callable[[Any], str] = str
else:
sys.dont_write_bytecode = True
unicode = unicode # noqa: F821 # pylint: disable=undefined-variable,self-assigning-variable

View file

@ -666,15 +666,15 @@ def run_argparse(
ap2 = ap.add_argument_group("Zeroconf options")
ap2.add_argument("-z", action="store_true", help="enable all zeroconf backends (mdns, ssdp)")
ap2.add_argument("--z-on", metavar="NICS/NETS", type=u, default="", help="enable zeroconf ONLY on the comma-separated list of subnets and/or interface names\n └─example: \033[32meth0, wlo1, virhost0, 192.168.123.0/24, fd00:fda::/96\033[0m")
ap2.add_argument("--z-off", metavar="NICS/NETS", type=u, default="", help="disable zeroconf on the comma-separated list of subnets and/or interface names")
ap2.add_argument("--z-on", metavar="NICS/NETS", type=u, default="", help="enable zeroconf ONLY on the comma-separated list of subnets and/or interface names/indexes\n └─example: \033[32meth0, wlo1, virhost0, 192.168.123.0/24, fd00:fda::/96\033[0m")
ap2.add_argument("--z-off", metavar="NICS/NETS", type=u, default="", help="disable zeroconf on the comma-separated list of subnets and/or interface names/indexes")
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-on", metavar="NICS/NETS", type=u, default="", help="enable zeroconf ONLY on the comma-separated list of subnets and/or interface names")
ap2.add_argument("--zm-off", metavar="NICS/NETS", type=u, default="", help="disable zeroconf on the comma-separated list of subnets and/or interface names")
ap2.add_argument("--zm-on", metavar="NICS/NETS", type=u, default="", help="enable zeroconf ONLY on the comma-separated list of subnets and/or interface names/indexes")
ap2.add_argument("--zm-off", metavar="NICS/NETS", type=u, default="", help="disable zeroconf on the comma-separated list of subnets and/or interface names/indexes")
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("--zmv", action="store_true", help="verbose mdns")
@ -690,8 +690,8 @@ def run_argparse(
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("--zs-on", metavar="NICS/NETS", type=u, default="", help="enable zeroconf ONLY on the comma-separated list of subnets and/or interface names")
ap2.add_argument("--zs-off", metavar="NICS/NETS", type=u, default="", help="disable zeroconf on the comma-separated list of subnets and/or interface names")
ap2.add_argument("--zs-on", metavar="NICS/NETS", type=u, default="", help="enable zeroconf ONLY on the comma-separated list of subnets and/or interface names/indexes")
ap2.add_argument("--zs-off", metavar="NICS/NETS", type=u, default="", help="disable zeroconf on the comma-separated list of subnets and/or interface names/indexes")
ap2.add_argument("--zsv", action="store_true", help="verbose SSDP")
ap2.add_argument("--zsl", metavar="PATH", type=u, default="/?hc", 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")

View file

@ -34,7 +34,6 @@ from .util import (
if True: # pylint: disable=using-constant-test
from collections.abc import Iterable
import typing
from typing import Any, Generator, Optional, Union
from .util import RootLogger

View file

@ -38,6 +38,7 @@ from .util import (
Garda,
Magician,
NetMap,
Netdev,
ipnorm,
min_ex,
shut_socket,
@ -140,7 +141,7 @@ class HttpSrv(object):
except:
pass
def set_netdevs(self, netdevs: dict[str, str]) -> None:
def set_netdevs(self, netdevs: dict[str, Netdev]) -> None:
self.nm = NetMap([self.ip], netdevs)
def start_threads(self, n: int) -> None:

View file

@ -25,7 +25,7 @@ from .stolen.dnslib import (
DNSQuestion,
DNSRecord,
)
from .util import CachedSet, Daemon, min_ex
from .util import CachedSet, Daemon, Netdev, min_ex
if TYPE_CHECKING:
from .svchub import SvcHub
@ -42,13 +42,12 @@ class MDNS_Sck(MC_Sck):
def __init__(
self,
sck: socket.socket,
idx: int,
name: str,
nd: Netdev,
grp: str,
ip: str,
net: Union[IPv4Network, IPv6Network],
):
super(MDNS_Sck, self).__init__(sck, idx, name, grp, ip, net)
super(MDNS_Sck, self).__init__(sck, nd, grp, ip, net)
self.bp_probe = b""
self.bp_ip = b""
@ -263,7 +262,8 @@ class MDNS(MCast):
try:
bound = self.create_servers()
except:
self.log("no server IP matches the mdns config", 1)
t = "no server IP matches the mdns config\n{}"
self.log(t.format(min_ex()), 1)
bound = []
if not bound:

View file

@ -7,8 +7,8 @@ import time
import ipaddress
from ipaddress import IPv4Address, IPv4Network, IPv6Address, IPv6Network
from .__init__ import MACOS, TYPE_CHECKING
from .util import min_ex, spack
from .__init__ import TYPE_CHECKING
from .util import Netdev, min_ex, spack
if TYPE_CHECKING:
from .svchub import SvcHub
@ -30,15 +30,14 @@ class MC_Sck(object):
def __init__(
self,
sck: socket.socket,
idx: int,
name: str,
nd: Netdev,
grp: str,
ip: str,
net: Union[IPv4Network, IPv6Network],
):
self.sck = sck
self.idx = idx
self.name = name
self.idx = nd.idx
self.name = nd.name
self.grp = grp
self.mreq = b""
self.ip = ip
@ -112,7 +111,7 @@ class MCast(object):
for lst in (on, off):
for av in list(lst):
for sk, sv in netdevs.items():
if av == sv.split(",")[0] and sk not in lst:
if (av == str(sv.idx) or av == sv.name) and sk not in lst:
lst.append(sk)
if on:
@ -137,12 +136,8 @@ class MCast(object):
for ip in ips:
v6 = ":" in ip
netdev = "?"
try:
netdev = netdevs[ip].split(",")[0]
idx = socket.if_nametoindex(netdev)
except:
idx = socket.INADDR_ANY
netdev = netdevs[ip]
if not netdev.idx:
t = "using INADDR_ANY for ip [{}], netdev [{}]"
if not self.srv and ip not in ["::", "0.0.0.0"]:
self.log(t.format(ip, netdev), 3)
@ -159,20 +154,14 @@ class MCast(object):
# most ipv6 clients expect multicast on linklocal ip only;
# add a/aaaa records for the other nic IPs
other_ips: set[str] = set()
if v6 and netdev not in ("?", ""):
for oip, onic in netdevs.items():
if (
onic.split(",")[0] == netdev
and oip in all_selected
and ":" in oip
):
other_ips.add(oip)
if v6:
for nd in netdevs.values():
if nd.idx == netdev.idx and nd.ip in all_selected and ":" in nd.ip:
other_ips.add(nd.ip)
net = ipaddress.ip_network(ip, False)
ip = ip.split("/")[0]
srv = self.Srv(
sck, idx, netdev, self.grp6 if ":" in ip else self.grp4, ip, net
)
srv = self.Srv(sck, netdev, self.grp6 if ":" in ip else self.grp4, ip, net)
for oth_ip in other_ips:
srv.ips[oth_ip.split("/")[0]] = ipaddress.ip_network(oth_ip, False)
@ -225,8 +214,11 @@ class MCast(object):
self.b2srv[bip] = srv
self.b6.append(bip)
grp = self.grp6 if srv.idx and not MACOS else ""
grp = self.grp6 if srv.idx else ""
try:
sck.bind((grp, self.port, 0, srv.idx))
except:
sck.bind(("", self.port, 0, srv.idx))
bgrp = socket.inet_pton(socket.AF_INET6, self.grp6)
dev = spack(b"@I", srv.idx)
@ -249,8 +241,12 @@ class MCast(object):
self.b2srv[bip] = srv
self.b4.append(bip)
grp = self.grp4 if srv.idx and not MACOS else ""
grp = self.grp4 if srv.idx else ""
try:
sck.bind((grp, self.port))
except:
sck.bind(("", self.port))
bgrp = socket.inet_aton(self.grp4)
dev = (
spack(b"=I", socket.INADDR_ANY)

View file

@ -105,7 +105,8 @@ class SSDPd(MCast):
try:
bound = self.create_servers()
except:
self.log("no server IP matches the ssdp config", 1)
t = "no server IP matches the ssdp config\n{}"
self.log(t.format(min_ex()), 1)
bound = []
if not bound:
@ -130,7 +131,7 @@ class SSDPd(MCast):
for sck in rx:
buf, addr = sck.recvfrom(4096)
try:
self.eat(buf, addr, sck)
self.eat(buf, addr)
except:
if not self.running:
return
@ -144,7 +145,7 @@ class SSDPd(MCast):
self.running = False
self.srv = {}
def eat(self, buf: bytes, addr: tuple[str, int], sck: socket.socket) -> None:
def eat(self, buf: bytes, addr: tuple[str, int]) -> None:
cip = addr[0]
if cip.startswith("169.254"):
return

View file

@ -6,14 +6,14 @@ import re
import socket
import sys
from .__init__ import ANYWIN, MACOS, PY2, TYPE_CHECKING, VT100, unicode
from .__init__ import ANYWIN, PY2, TYPE_CHECKING, VT100, unicode
from .stolen.qrcodegen import QrCode
from .util import (
E_ACCESS,
E_ADDR_IN_USE,
E_ADDR_NOT_AVAIL,
E_UNREACH,
chkcmd,
Netdev,
min_ex,
sunpack,
termsize,
@ -101,7 +101,10 @@ class TcpSrv(object):
if pad:
self.log("tcpsrv", "")
eps = {"127.0.0.1": "local only", "::1": "local only"}
eps = {
"127.0.0.1": Netdev("127.0.0.1", 0, "", "local only"),
"::1": Netdev("::1", 0, "", "local only"),
}
nonlocals = [x for x in self.args.i if x not in [k.split("/")[0] for k in eps]]
if nonlocals:
try:
@ -114,7 +117,7 @@ class TcpSrv(object):
eps.update({k.split("/")[0]: v for k, v in self.netdevs.items()})
if not eps:
for x in nonlocals:
eps[x] = "external"
eps[x] = Netdev(x, 0, "", "external")
else:
self.netdevs = {}
@ -264,143 +267,11 @@ class TcpSrv(object):
self.log("tcpsrv", "ok bye")
def ips_linux_ifconfig(self) -> dict[str, str]:
# for termux
try:
txt, _ = chkcmd(["ifconfig"])
except:
return {}
eps: dict[str, str] = {}
dev = None
ip = None
up = None
for ln in (txt + "\n").split("\n"):
if not ln.strip() and dev and ip:
eps[ip] = dev + ("" if up else ", \033[31mLINK-DOWN")
dev = ip = up = None
continue
if ln == ln.lstrip():
dev = re.split(r"[: ]", ln)[0]
if "UP" in re.split(r"[<>, \t]", ln):
up = True
m = re.match(r"^\s+inet\s+([^ ]+)", ln)
if m:
ip = m.group(1)
return eps
def ips_linux(self) -> dict[str, str]:
try:
txt, _ = chkcmd(["ip", "addr"])
except:
return self.ips_linux_ifconfig()
r = re.compile(r"^\s+inet6? ([^ ]+)/")
ri = re.compile(r"^[0-9]+: ([^:]+): ")
dev = ""
up = False
eps: dict[str, str] = {}
for ln in txt.split("\n"):
m = ri.match(ln)
if m:
dev = m.group(1)
up = "UP" in re.split("[>,< ]", ln)
m = r.match(ln.rstrip())
if not m or not dev or " scope link" in ln:
continue
ip = m.group(1)
eps[ip] = dev + ("" if up else ", \033[31mLINK-DOWN")
return eps
def ips_macos(self) -> dict[str, str]:
eps: dict[str, str] = {}
try:
txt, _ = chkcmd(["ifconfig"])
except:
return eps
rdev = re.compile(r"^([^ ]+):")
rip = re.compile(r"^\tinet ([0-9\.]+) ")
dev = "UNKNOWN"
for ln in txt.split("\n"):
m = rdev.match(ln)
if m:
dev = m.group(1)
m = rip.match(ln)
if m:
eps[m.group(1)] = dev
dev = "UNKNOWN"
return eps
def ips_windows_ipconfig(self) -> tuple[dict[str, str], set[str]]:
eps: dict[str, str] = {}
offs: set[str] = set()
try:
txt, _ = chkcmd(["ipconfig"])
except:
return eps, offs
rdev = re.compile(r"(^[^ ].*):$")
rip = re.compile(r"^ +IPv?4? [^:]+: *([0-9\.]{7,15})$")
roff = re.compile(r".*: Media disconnected$")
dev = None
for ln in txt.replace("\r", "").split("\n"):
m = rdev.match(ln)
if m:
if dev and dev not in eps.values():
offs.add(dev)
dev = m.group(1).split(" adapter ", 1)[-1]
if dev and roff.match(ln):
offs.add(dev)
dev = None
m = rip.match(ln)
if m and dev:
eps[m.group(1)] = dev
dev = None
if dev and dev not in eps.values():
offs.add(dev)
return eps, offs
def ips_windows_netsh(self) -> dict[str, str]:
eps: dict[str, str] = {}
try:
txt, _ = chkcmd("netsh interface ip show address".split())
except:
return eps
rdev = re.compile(r'.* "([^"]+)"$')
rip = re.compile(r".* IP\b.*: +([0-9\.]{7,15})$")
dev = None
for ln in txt.replace("\r", "").split("\n"):
m = rdev.match(ln)
if m:
dev = m.group(1)
m = rip.match(ln)
if m and dev:
eps[m.group(1)] = dev
return eps
def detect_interfaces(self, listen_ips: list[str]) -> dict[str, str]:
def detect_interfaces(self, listen_ips: list[str]) -> dict[str, Netdev]:
from .stolen.ifaddr import get_adapters
nics = get_adapters(True)
eps = {}
eps: dict[str, Netdev] = {}
for nic in nics:
for nip in nic.ips:
ipa = nip.ip[0] if ":" in str(nip.ip) else nip.ip
@ -409,14 +280,15 @@ class TcpSrv(object):
# browsers dont impl linklocal
continue
eps[sip] = nic.nice_name
eps[sip] = Netdev(sip, nic.index or 0, nic.nice_name, "")
if "0.0.0.0" not in listen_ips and "::" not in listen_ips:
eps = {k: v for k, v in eps.items() if k.split("/")[0] in listen_ips}
try:
ext_devs = list(self._extdevs_nix())
ext_ips = [k for k, v in eps.items() if v.split(",")[0] in ext_devs]
ext_ips = [k for k, v in eps.items() if v.name in ext_devs]
ext_ips = [x.split("/")[0] for x in ext_ips]
if not ext_ips:
raise Exception()
except:
@ -430,11 +302,9 @@ class TcpSrv(object):
desc = "\033[32mexternal"
ips = ext_ips if lip in ["0.0.0.0", "::"] else [lip]
for ip in ips:
try:
if "external" not in eps[ip]:
eps[ip] += ", " + desc
except:
eps[ip] = desc
ip = next((x for x in eps if x.startswith(ip + "/")), "")
if ip and "external" not in eps[ip].desc:
eps[ip].desc += ", " + desc
return eps

View file

@ -188,10 +188,14 @@ IMPLICATIONS = [
["z", "zm"],
["z", "zs"],
["zmvv", "zmv"],
["zm4", "zm"],
["zm6", "zm"],
["zmv", "zm"],
["zms", "zm"],
["zsv", "zs"],
]
if ANYWIN:
IMPLICATIONS.extend([["z", "zm4"]])
UNPLICATIONS = [["no_dav", "daw"]]
@ -363,6 +367,23 @@ class Daemon(threading.Thread):
self.start()
class Netdev(object):
def __init__(self, ip: str, idx: int, name: str, desc: str):
self.ip = ip
self.idx = idx
self.name = name
self.desc = desc
def __str__(self):
return "{}-{}{}".format(self.idx, self.name, self.desc)
def __lt__(self, rhs):
return str(self) < str(rhs)
def __eq__(self, rhs):
return str(self) == str(rhs)
class Cooldown(object):
def __init__(self, maxage: float) -> None:
self.maxage = maxage
@ -434,7 +455,7 @@ class HLog(logging.Handler):
class NetMap(object):
def __init__(self, ips: list[str], netdevs: dict[str, str]) -> None:
def __init__(self, ips: list[str], netdevs: dict[str, Netdev]) -> None:
if "::" in ips:
ips = [x for x in ips if x != "::"] + list(
[x.split("/")[0] for x in netdevs if ":" in x]
@ -1791,11 +1812,14 @@ if not PY2 or not WINDOWS:
else:
# moonrunes become \x3f with bytestrings,
# losing mojibake support is worth
def _not_actually_mbcs(txt: str) -> str:
def _not_actually_mbcs_enc(txt: str) -> bytes:
return txt
fsenc = _not_actually_mbcs
fsdec = _not_actually_mbcs
def _not_actually_mbcs_dec(txt: bytes) -> str:
return txt
fsenc = _not_actually_mbcs_enc
fsdec = _not_actually_mbcs_dec
def s3enc(mem_cur: "sqlite3.Cursor", rd: str, fn: str) -> tuple[str, str]:

View file

@ -109,6 +109,7 @@ pre, code {
html.z pre,
html.z code {
color: #9e0;
background: #000;
background: rgba(0,16,0,0.2);
}
.os {

View file

@ -138,7 +138,7 @@ tmpdir="$(
)"
necho() {
printf '\033[G%s\033[K' "$*"
printf '\033[G%s ... \033[K' "$*"
}
[ $repack ] && {
@ -331,7 +331,6 @@ find -name py.typed -delete
find -type f \( -name .DS_Store -or -name ._.DS_Store \) -delete
find -type f -name ._\* | while IFS= read -r f; do cmp <(printf '\x00\x05\x16') <(head -c 3 -- "$f") && rm -f -- "$f"; done
echo use smol web deps
rm -f copyparty/web/deps/*.full.* copyparty/web/dbg-* copyparty/web/Makefile
find copyparty | LC_ALL=C sort | sed 's/\.gz$//;s/$/,/' > have
@ -466,7 +465,7 @@ zdir="$tmpdir/cpp-mk$CSN"
[ -e "$zdir/$stamp" ] || rm -rf "$zdir"
mkdir -p "$zdir"
echo a > "$zdir/$stamp"
nf=$(ls -1 "$zdir"/arc.* | wc -l)
nf=$(ls -1 "$zdir"/arc.* 2>/dev/null | wc -l)
[ $nf -ge 2 ] && [ ! $repack ] && use_zdir=1 || use_zdir=
[ $use_zdir ] || {

View file

@ -3,6 +3,7 @@ set -e
curl -k https://192.168.123.1:3923/cpp/scripts/pyinstaller/build.sh |
tee build2.sh | cmp build.sh && rm build2.sh || {
[ -s build2.sh ] || exit 1
echo "new build script; upgrade y/n:"
while true; do read -u1 -n1 -r r; [[ $r =~ [yYnN] ]] && break; done
[[ $r =~ [yY] ]] && mv build{2,}.sh && exec ./build.sh