mdns ipv6 fixes; now works on ie11/safari, not linux:

* subscribe/announce on LL only
* add NSEC records if 4/6-only
This commit is contained in:
ed 2022-11-15 06:39:53 +00:00
parent 37c1cab726
commit 8829f56d4c
6 changed files with 91 additions and 25 deletions

View file

@ -19,6 +19,7 @@ from .stolen.dnslib import (
QTYPE,
A,
AAAA,
NSEC,
SRV,
PTR,
TXT,
@ -41,11 +42,12 @@ class MDNS_Sck(MC_Sck):
self,
sck: socket.socket,
idx: int,
name: str,
grp: str,
ip: str,
net: Union[IPv4Network, IPv6Network],
):
super(MDNS_Sck, self).__init__(sck, idx, grp, ip, net)
super(MDNS_Sck, self).__init__(sck, idx, name, grp, ip, net)
self.bp_probe = b""
self.bp_ip = b""
@ -143,6 +145,16 @@ class MDNS(MCast):
sreply = DNSRecord(DNSHeader(0, 0x8400))
bye = DNSRecord(DNSHeader(0, 0x8400))
have4 = have6 = False
for s2 in self.srv.values():
if srv.idx != s2.idx:
continue
if s2.v6:
have6 = True
else:
have4 = True
for ip in srv.ips:
if ":" in ip:
qt = QTYPE.AAAA
@ -162,6 +174,12 @@ class MDNS(MCast):
sreply.add_answer(r120)
bye.add_answer(r0)
if not have4 or not have6:
ns = NSEC(self.hn, ["AAAA" if have4 else "A"])
r = RR(self.hn, QTYPE.NSEC, DC.F_IN, 120, ns)
areply.add_ar(r)
sreply.add_ar(r)
for sclass, props in self.svcs.items():
sname = props["name"]
sport = props["port"]
@ -255,13 +273,13 @@ class MDNS(MCast):
rx: list[socket.socket] = rdy[0] # type: ignore
self.rx4.cln()
self.rx6.cln()
for srv in rx:
buf, addr = srv.recvfrom(4096)
for sck in rx:
buf, addr = sck.recvfrom(4096)
try:
self.eat(buf, addr)
self.eat(buf, addr, sck)
except:
t = "{} \033[33m|{}| {}\n{}".format(
addr, len(buf), repr(buf)[2:-1], min_ex()
t = "{} {} \033[33m|{}| {}\n{}".format(
self.srv[sck].name, addr, len(buf), repr(buf)[2:-1], min_ex()
)
self.log(t, 6)
@ -279,9 +297,11 @@ class MDNS(MCast):
for srv in self.srv.values():
srv.sck.sendto(srv.bp_bye, (srv.grp, 5353))
def eat(self, buf: bytes, addr: tuple[str, int]):
self.srv = {}
def eat(self, buf: bytes, addr: tuple[str, int], sck: socket.socket):
cip = addr[0]
if cip.startswith("fe80") or cip.startswith("169.254"):
if cip.startswith("169.254"):
return
v6 = ":" in cip
@ -290,14 +310,15 @@ class MDNS(MCast):
return
cache.add(buf)
srv: Optional[MDNS_Sck] = self.map_client(cip) # type: ignore
srv: Optional[MDNS_Sck] = self.srv[sck] if v6 else self.map_client(cip) # type: ignore
if not srv:
return
now = time.time()
if self.args.zmv:
self.log("[{}] \033[36m{} \033[0m|{}|".format(srv.ip, cip, len(buf)), "90")
t = "{} [{}] \033[36m{} \033[0m|{}|"
self.log(t.format(srv.name, srv.ip, cip, len(buf)), "90")
p = DNSRecord.parse(buf)
if self.args.zmvv:
@ -336,8 +357,8 @@ class MDNS(MCast):
else:
t += "Emergency stop; hostname '{}' got stolen"
t += "! Use --name to set another hostname.\n\nName taken by {}\n\nYour IPs: {}\n"
self.log(t.format(self.args.name, cips, list(self.sips)), 1)
t += " on {}! Use --name to set another hostname.\n\nName taken by {}\n\nYour IPs: {}\n"
self.log(t.format(self.args.name, srv.name, cips, list(self.sips)), 1)
self.stop(True)
return
@ -348,11 +369,17 @@ class MDNS(MCast):
# gvfs keeps repeating itself
found = False
unicast = False
for r in p.rr:
rname = U(r.rname).lower()
if rname == self.hn and r.ttl > 60:
found = True
break
if rname == self.hn:
if r.ttl > 60:
found = True
if r.rclass == DC.F_IN:
unicast = True
if unicast:
srv.sck.sendto(srv.bp_ip, (cip, 5353))
if not found:
self.q[cip] = (0, srv, srv.bp_ip)

View file

@ -26,12 +26,14 @@ class MC_Sck(object):
self,
sck: socket.socket,
idx: int,
name: str,
grp: str,
ip: str,
net: Union[IPv4Network, IPv6Network],
):
self.sck = sck
self.idx = idx
self.name = name
self.grp = grp
self.mreq = b""
self.ip = ip
@ -55,7 +57,7 @@ class MCast(object):
self.port = port
self.srv: dict[socket.socket, MC_Sck] = {} # listening sockets
self.sips: set[str] = set() # all listening ips
self.sips: set[str] = set() # all listening ips (including failed attempts)
self.b2srv: dict[bytes, MC_Sck] = {} # binary-ip -> server socket
self.b4: list[bytes] = [] # sorted list of binary-ips
self.b6: list[bytes] = [] # sorted list of binary-ips
@ -82,6 +84,7 @@ class MCast(object):
ips = [x for x in ips if x not in ("::1", "127.0.0.1")]
# ip -> ip/prefix
ips = [
[x for x in self.hub.tcpsrv.netdevs if x.startswith(y + "/")][0]
for y in ips
@ -93,6 +96,10 @@ class MCast(object):
if not self.grp6:
ips = [x for x in ips if ":" not in x]
# discard non-linklocal ipv6
all_selected = ips[:]
ips = [x for x in ips if ":" not in x or x.startswith("fe80")]
if not ips:
raise Exception("no server IP matches the mdns config")
@ -117,16 +124,39 @@ class MCast(object):
except:
pass
# 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 self.hub.tcpsrv.netdevs.items():
if (
onic.split(",")[0] == netdev
and oip in all_selected
and ":" in oip
):
other_ips.add(oip)
net = ipaddress.ip_network(ip, False)
ip = ip.split("/")[0]
srv = self.Srv(sck, idx, self.grp6 if ":" in ip else self.grp4, ip, net)
srv = self.Srv(
sck, idx, 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)
# gvfs breaks if a linklocal ip appears in a dns reply
srv.ips = {k: v for k, v in srv.ips.items() if not k.startswith("fe80")}
if not srv.ips:
self.log("no routable IPs on {}; skipping [{}]".format(netdev, ip), 3)
continue
try:
self.setup_socket(srv)
self.srv[sck] = srv
bound.append(ip)
except:
self.log("announce failed on [{}]:\n{}".format(ip, min_ex()))
t = "announce failed on {} [{}]:\n{}"
self.log(t.format(netdev, ip, min_ex()), 3)
if self.args.zm_msub:
for s1 in self.srv.values():
@ -145,18 +175,22 @@ class MCast(object):
if net1 == net2 and ip1 != ip2:
s1.ips[ip2] = net2
self.sips = set([x.ip for x in self.srv.values()])
self.sips = set([x.split("/")[0] for x in all_selected])
for srv in self.srv.values():
assert srv.ip in self.sips
return bound
def setup_socket(self, srv: MC_Sck) -> None:
sck = srv.sck
if srv.v6:
if self.args.zmv:
self.log("v6({}) idx({})".format(srv.ip, srv.idx), 6)
self.log("v6({}) idx({}) {}".format(srv.ip, srv.idx, srv.ips), 6)
bip = socket.inet_pton(socket.AF_INET6, srv.ip)
self.b2srv[bip] = srv
self.b6.append(bip)
for ip in srv.ips:
bip = socket.inet_pton(socket.AF_INET6, ip)
self.b2srv[bip] = srv
self.b6.append(bip)
sck.bind((self.grp6 if srv.idx else "", self.port, 0, srv.idx))
bgrp = socket.inet_pton(socket.AF_INET6, self.grp6)

View file

@ -764,7 +764,7 @@ class NSEC(RD):
label = property(get_label, set_label)
def pack(self, buffer):
buffer.encode_name_nocompress(self.label)
buffer.encode_name(self.label)
buffer.append(encode_type_bitmap(self.rrlist))
def __repr__(self):

View file

@ -397,7 +397,7 @@ class TcpSrv(object):
for nip in nic.ips:
ipa = nip.ip[0] if ":" in str(nip.ip) else nip.ip
sip = "{}/{}".format(ipa, nip.network_prefix)
if sip.startswith("fe80") or sip.startswith("169.254"):
if sip.startswith("169.254"):
# browsers dont impl linklocal
continue

View file

@ -16,6 +16,10 @@ https://github.com/paulc/dnslib/
C: 2010-2017 Paul Chakravarti
L: BSD 2-Clause
https://github.com/pydron/ifaddr/
C: 2014 Stefan C. Mueller
L: BSD-2-Clause
https://github.com/giampaolo/pyftpdlib/
C: 2007 Giampaolo Rodola
L: MIT

View file

@ -19,6 +19,7 @@ copyparty/httpconn.py,
copyparty/httpsrv.py,
copyparty/ico.py,
copyparty/mdns.py,
copyparty/multicast.py,
copyparty/mtag.py,
copyparty/res,
copyparty/res/COPYING.txt,