From 35175fd68517abf699b78a73329d4075dda46162 Mon Sep 17 00:00:00 2001 From: ed Date: Sun, 20 Nov 2022 20:31:11 +0000 Subject: [PATCH] mdns: support primitive clients (android, rfc-6.7) --- copyparty/__main__.py | 2 +- copyparty/mdns.py | 64 +++++++++++++++++++++++++++++----- copyparty/stolen/dnslib/dns.py | 6 ++-- copyparty/tcpsrv.py | 7 ++-- 4 files changed, 64 insertions(+), 15 deletions(-) diff --git a/copyparty/__main__.py b/copyparty/__main__.py index ad15f7e3..46300807 100755 --- a/copyparty/__main__.py +++ b/copyparty/__main__.py @@ -621,7 +621,7 @@ def run_argparse( ap2.add_argument("--qr", action="store_true", help="show http:// QR-code on startup") ap2.add_argument("--qrs", action="store_true", help="show https:// QR-code on startup") ap2.add_argument("--qrl", metavar="PATH", type=u, default="", help="location to include in the url, for example [\033[32mpriv/?pw=hunter2\033[0m]") - ap2.add_argument("--qri", metavar="PREFIX", type=u, default="", help="select IP which starts with PREFIX") + ap2.add_argument("--qri", metavar="PREFIX", type=u, default="", help="select IP which starts with PREFIX; [\033[32m.\033[0m] to force default IP when mDNS URL would have been used instead") ap2.add_argument("--qr-fg", metavar="COLOR", type=int, default=0 if tty else 16, help="foreground; try [\033[32m0\033[0m] if the qr-code is unreadable") ap2.add_argument("--qr-bg", metavar="COLOR", type=int, default=229, help="background (white=255)") ap2.add_argument("--qrp", metavar="CELLS", type=int, default=4, help="padding (spec says 4 or more, but 1 is usually fine)") diff --git a/copyparty/mdns.py b/copyparty/mdns.py index 0f93c62d..9f4ee850 100644 --- a/copyparty/mdns.py +++ b/copyparty/mdns.py @@ -373,24 +373,66 @@ class MDNS(MCast): self.stop(True) return + # then rfc-6.7; dns pretending to be mdns (android...) + if p.header.id or addr[1] != 5353: + rsp: Optional[DNSRecord] = None + for r in p.questions: + try: + lhn = U(r.qname).lower() + except: + self.log("invalid question: {}".format(r)) + continue + + if lhn != self.lhn: + continue + + if p.header.id and r.qtype in (QTYPE.A, QTYPE.AAAA): + rsp = rsp or DNSRecord(DNSHeader(p.header.id, 0x8400)) + rsp.add_question(r) + for ip in srv.ips: + qt = r.qtype + v6 = ":" in ip + if v6 == (qt == QTYPE.AAAA): + rd = AAAA(ip) if v6 else A(ip) + rr = RR(self.hn, qt, DC.IN, 10, rd) + rsp.add_answer(rr) + if rsp: + srv.sck.sendto(rsp.pack(), addr[:2]) + # but don't return in case it's a differently broken client + # then a/aaaa records for r in p.questions: - if U(r.qname).lower() != self.lhn: + try: + lhn = U(r.qname).lower() + except: + self.log("invalid question: {}".format(r)) + continue + + if lhn != self.lhn: continue # gvfs keeps repeating itself found = False unicast = False - for r in p.rr: - rname = U(r.rname).lower() + for rr in p.rr: + try: + rname = U(rr.rname).lower() + except: + self.log("invalid rr: {}".format(rr)) + continue + if rname == self.lhn: - if r.ttl > 60: + if rr.ttl > 60: found = True - if r.rclass == DC.F_IN: + if rr.rclass == DC.F_IN: unicast = True if unicast: + # spec-compliant mDNS-over-unicast srv.sck.sendto(srv.bp_ip, (cip, 5353)) + elif addr[1] != 5353: + # just in case some clients use (and want us to use) invalid ports + srv.sck.sendto(srv.bp_ip, addr[:2]) if not found: self.q[cip] = (0, srv, srv.bp_ip) @@ -400,6 +442,9 @@ class MDNS(MCast): # and service queries for r in p.questions: + if not r or not r.qname: + continue + qname = U(r.qname).lower() if qname in self.lsvcs or qname == "_services._dns-sd._udp.local.": self.q[cip] = (deadline, srv, srv.bp_svc) @@ -408,10 +453,13 @@ class MDNS(MCast): # (workaround gvfs race-condition where it occasionally # doesn't read/decode the full response...) if now < srv.last_tx + 12: - for r in p.rr: - rdata = U(r.rdata).lower() + for rr in p.rr: + if not rr.rdata: + continue + + rdata = U(rr.rdata).lower() if rdata in self.lsfqdns: - if r.ttl > 2250: + if rr.ttl > 2250: self.q.pop(cip, None) break diff --git a/copyparty/stolen/dnslib/dns.py b/copyparty/stolen/dnslib/dns.py index 57cda37f..2bd6b680 100644 --- a/copyparty/stolen/dnslib/dns.py +++ b/copyparty/stolen/dnslib/dns.py @@ -106,9 +106,9 @@ class DNSRecord(object): ) -> None: self.header = header or DNSHeader() self.questions: list[DNSQuestion] = questions or [] - self.rr = rr or [] - self.auth = auth or [] - self.ar = ar or [] + self.rr: list[RR] = rr or [] + self.auth: list[RR] = auth or [] + self.ar: list[RR] = ar or [] if q: self.questions.append(q) diff --git a/copyparty/tcpsrv.py b/copyparty/tcpsrv.py index ac62ebbd..77d50767 100644 --- a/copyparty/tcpsrv.py +++ b/copyparty/tcpsrv.py @@ -508,19 +508,20 @@ class TcpSrv(object): def _qr(self, t1: dict[str, list[int]], t2: dict[str, list[int]]) -> str: ip = None ips = list(t1) + list(t2) - if self.args.zm: + qri = self.args.qri + if self.args.zm and not qri: name = self.args.name + ".local" t1[name] = next(v for v in (t1 or t2).values()) ips = [name] + ips for ip in ips: - if ip.startswith(self.args.qri): + if ip.startswith(qri) or qri == ".": break ip = "" if not ip: # maybe /bin/ip is missing or smth - ip = self.args.qri + ip = qri if not ip: return ""