mirror of
https://github.com/9001/copyparty.git
synced 2025-08-17 17:12:13 -06:00
add native ipv6 support
This commit is contained in:
parent
e442cb677a
commit
c72753c5da
|
@ -144,6 +144,7 @@ recommended additional steps on debian which enable audio metadata and thumbnai
|
||||||
## features
|
## features
|
||||||
|
|
||||||
* backend stuff
|
* backend stuff
|
||||||
|
* ☑ IPv6
|
||||||
* ☑ [multiprocessing](#performance) (actual multithreading)
|
* ☑ [multiprocessing](#performance) (actual multithreading)
|
||||||
* ☑ volumes (mountpoints)
|
* ☑ volumes (mountpoints)
|
||||||
* ☑ [accounts](#accounts-and-volumes)
|
* ☑ [accounts](#accounts-and-volumes)
|
||||||
|
@ -223,6 +224,9 @@ browser-specific:
|
||||||
* Desktop-Firefox: ~~may use gigabytes of RAM if your files are massive~~ *seems to be OK now*
|
* Desktop-Firefox: ~~may use gigabytes of RAM if your files are massive~~ *seems to be OK now*
|
||||||
* Desktop-Firefox: may stop you from deleting files you've uploaded until you visit `about:memory` and click `Minimize memory usage`
|
* Desktop-Firefox: may stop you from deleting files you've uploaded until you visit `about:memory` and click `Minimize memory usage`
|
||||||
|
|
||||||
|
server-os-specific:
|
||||||
|
* RHEL8 / Rocky8: you can run copyparty using `/usr/libexec/platform-python`
|
||||||
|
|
||||||
|
|
||||||
# bugs
|
# bugs
|
||||||
|
|
||||||
|
@ -756,7 +760,7 @@ some **BIG WARNINGS** specific to SMB/CIFS, in decreasing importance:
|
||||||
and some minor issues,
|
and some minor issues,
|
||||||
* clients only see the first ~400 files in big folders; [impacket#1433](https://github.com/SecureAuthCorp/impacket/issues/1433)
|
* clients only see the first ~400 files in big folders; [impacket#1433](https://github.com/SecureAuthCorp/impacket/issues/1433)
|
||||||
* hot-reload of server config (`/?reload=cfg`) only works for volumes, not account passwords
|
* hot-reload of server config (`/?reload=cfg`) only works for volumes, not account passwords
|
||||||
* listens on the first `-i` interface only (default = 0.0.0.0 = all)
|
* listens on the first IPv4 `-i` interface only (default = :: = 0.0.0.0 = all)
|
||||||
* login doesn't work on winxp, but anonymous access is ok -- remove all accounts from copyparty config for that to work
|
* login doesn't work on winxp, but anonymous access is ok -- remove all accounts from copyparty config for that to work
|
||||||
* win10 onwards does not allow connecting anonymously / without accounts
|
* win10 onwards does not allow connecting anonymously / without accounts
|
||||||
* on windows, creating a new file through rightclick --> new --> textfile throws an error due to impacket limitations -- hit OK and F5 to get your file
|
* on windows, creating a new file through rightclick --> new --> textfile throws an error due to impacket limitations -- hit OK and F5 to get your file
|
||||||
|
|
|
@ -613,7 +613,7 @@ def run_argparse(
|
||||||
ap2.add_argument("--write-uplog", action="store_true", help="write POST reports to textfiles in working-directory")
|
ap2.add_argument("--write-uplog", action="store_true", help="write POST reports to textfiles in working-directory")
|
||||||
|
|
||||||
ap2 = ap.add_argument_group('network options')
|
ap2 = ap.add_argument_group('network options')
|
||||||
ap2.add_argument("-i", metavar="IP", type=u, default="0.0.0.0", help="ip to bind (comma-sep.)")
|
ap2.add_argument("-i", metavar="IP", type=u, default="::", help="ip to bind (comma-sep.), default: all IPv4 and IPv6")
|
||||||
ap2.add_argument("-p", metavar="PORT", type=u, default="3923", help="ports to bind (comma/range)")
|
ap2.add_argument("-p", metavar="PORT", type=u, default="3923", help="ports to bind (comma/range)")
|
||||||
ap2.add_argument("--rproxy", metavar="DEPTH", type=int, default=1, help="which ip to keep; [\033[32m0\033[0m]=tcp, [\033[32m1\033[0m]=origin (first x-fwd), [\033[32m2\033[0m]=cloudflare, [\033[32m3\033[0m]=nginx, [\033[32m-1\033[0m]=closest proxy")
|
ap2.add_argument("--rproxy", metavar="DEPTH", type=int, default=1, help="which ip to keep; [\033[32m0\033[0m]=tcp, [\033[32m1\033[0m]=origin (first x-fwd), [\033[32m2\033[0m]=cloudflare, [\033[32m3\033[0m]=nginx, [\033[32m-1\033[0m]=closest proxy")
|
||||||
ap2.add_argument("--s-wr-sz", metavar="B", type=int, default=256*1024, help="socket write size in bytes")
|
ap2.add_argument("--s-wr-sz", metavar="B", type=int, default=256*1024, help="socket write size in bytes")
|
||||||
|
|
|
@ -1366,7 +1366,7 @@ class HttpCli(object):
|
||||||
|
|
||||||
url = "{}://{}/{}".format(
|
url = "{}://{}/{}".format(
|
||||||
"https" if self.is_https else "http",
|
"https" if self.is_https else "http",
|
||||||
self.headers.get("host") or "{}:{}".format(*list(self.s.getsockname())),
|
self.headers.get("host") or "{}:{}".format(*list(self.s.getsockname()[:2])),
|
||||||
vpath + vsuf,
|
vpath + vsuf,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1386,7 +1386,7 @@ class HttpCli(object):
|
||||||
else:
|
else:
|
||||||
t = "{}\n{}\n{}\n{}\n".format(post_sz, sha_b64, sha_hex[:56], url)
|
t = "{}\n{}\n{}\n{}\n".format(post_sz, sha_b64, sha_hex[:56], url)
|
||||||
|
|
||||||
h = {"Location": url} if is_put else {}
|
h = {"Location": url} if is_put and url else {}
|
||||||
self.reply(t.encode("utf-8"), 201, headers=h)
|
self.reply(t.encode("utf-8"), 201, headers=h)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -2037,7 +2037,7 @@ class HttpCli(object):
|
||||||
"url": "{}://{}/{}".format(
|
"url": "{}://{}/{}".format(
|
||||||
"https" if self.is_https else "http",
|
"https" if self.is_https else "http",
|
||||||
self.headers.get("host")
|
self.headers.get("host")
|
||||||
or "{}:{}".format(*list(self.s.getsockname())),
|
or "{}:{}".format(*list(self.s.getsockname()[:2])),
|
||||||
rel_url,
|
rel_url,
|
||||||
),
|
),
|
||||||
"sha512": sha_hex[:56],
|
"sha512": sha_hex[:56],
|
||||||
|
|
|
@ -174,7 +174,7 @@ 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()
|
ip, 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(
|
||||||
|
@ -185,9 +185,10 @@ class HttpSrv(object):
|
||||||
|
|
||||||
def thr_listen(self, srv_sck: socket.socket) -> None:
|
def thr_listen(self, srv_sck: socket.socket) -> None:
|
||||||
"""listens on a shared tcp server"""
|
"""listens on a shared tcp server"""
|
||||||
ip, port = srv_sck.getsockname()
|
ip, port = srv_sck.getsockname()[:2]
|
||||||
fno = srv_sck.fileno()
|
fno = srv_sck.fileno()
|
||||||
msg = "subscribed @ {}:{} f{} p{}".format(ip, port, fno, os.getpid())
|
hip = "[{}]".format(ip) if ":" in ip else ip
|
||||||
|
msg = "subscribed @ {}:{} f{} p{}".format(hip, port, fno, os.getpid())
|
||||||
self.log(self.name, msg)
|
self.log(self.name, msg)
|
||||||
|
|
||||||
def fun() -> None:
|
def fun() -> None:
|
||||||
|
@ -261,7 +262,12 @@ class HttpSrv(object):
|
||||||
self.log(self.name, "|%sC-acc1" % ("-" * 2,), c="90")
|
self.log(self.name, "|%sC-acc1" % ("-" * 2,), c="90")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
sck, addr = srv_sck.accept()
|
sck, saddr = srv_sck.accept()
|
||||||
|
cip, cport = saddr[:2]
|
||||||
|
if cip.startswith("::ffff:"):
|
||||||
|
cip = cip[7:]
|
||||||
|
|
||||||
|
addr = (cip, cport)
|
||||||
except (OSError, socket.error) as ex:
|
except (OSError, socket.error) as ex:
|
||||||
self.log(self.name, "accept({}): {}".format(fno, ex), c=6)
|
self.log(self.name, "accept({}): {}".format(fno, ex), c=6)
|
||||||
time.sleep(0.02)
|
time.sleep(0.02)
|
||||||
|
|
|
@ -75,7 +75,11 @@ class SMB(object):
|
||||||
smbserver.isInFileJail = self._is_in_file_jail
|
smbserver.isInFileJail = self._is_in_file_jail
|
||||||
self._disarm()
|
self._disarm()
|
||||||
|
|
||||||
ip = self.args.i[0]
|
ip = next((x for x in self.args.i if ":" not in x), None)
|
||||||
|
if not ip:
|
||||||
|
self.log("smb", "IPv6 not supported for SMB; listening on 0.0.0.0", 3)
|
||||||
|
ip = "0.0.0.0"
|
||||||
|
|
||||||
port = int(self.args.smb_port)
|
port = int(self.args.smb_port)
|
||||||
srv = smbserver.SimpleSMBServer(listenAddress=ip, listenPort=port)
|
srv = smbserver.SimpleSMBServer(listenAddress=ip, listenPort=port)
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ from .util import (
|
||||||
E_ADDR_NOT_AVAIL,
|
E_ADDR_NOT_AVAIL,
|
||||||
E_UNREACH,
|
E_UNREACH,
|
||||||
chkcmd,
|
chkcmd,
|
||||||
|
min_ex,
|
||||||
sunpack,
|
sunpack,
|
||||||
termsize,
|
termsize,
|
||||||
)
|
)
|
||||||
|
@ -43,25 +44,57 @@ class TcpSrv(object):
|
||||||
self.srv: list[socket.socket] = []
|
self.srv: list[socket.socket] = []
|
||||||
self.nsrv = 0
|
self.nsrv = 0
|
||||||
self.qr = ""
|
self.qr = ""
|
||||||
|
pad = False
|
||||||
ok: dict[str, list[int]] = {}
|
ok: dict[str, list[int]] = {}
|
||||||
for ip in self.args.i:
|
for ip in self.args.i:
|
||||||
ok[ip] = []
|
if ip == "::":
|
||||||
|
if socket.has_ipv6:
|
||||||
|
ips = ["::", "0.0.0.0"]
|
||||||
|
dual = True
|
||||||
|
else:
|
||||||
|
ips = ["0.0.0.0"]
|
||||||
|
dual = False
|
||||||
|
else:
|
||||||
|
ips = [ip]
|
||||||
|
dual = False
|
||||||
|
|
||||||
|
for ipa in ips:
|
||||||
|
ok[ipa] = []
|
||||||
|
|
||||||
for port in self.args.p:
|
for port in self.args.p:
|
||||||
self.nsrv += 1
|
successful_binds = 0
|
||||||
try:
|
try:
|
||||||
self._listen(ip, port)
|
for ipa in ips:
|
||||||
ok[ip].append(port)
|
try:
|
||||||
|
self._listen(ipa, port)
|
||||||
|
ok[ipa].append(port)
|
||||||
|
successful_binds += 1
|
||||||
|
except:
|
||||||
|
if dual and ":" in ipa:
|
||||||
|
t = "listen on IPv6 [{}] failed; trying IPv4 {}...\n{}"
|
||||||
|
self.log("tcpsrv", t.format(ipa, ips[1], min_ex()), 3)
|
||||||
|
pad = True
|
||||||
|
continue
|
||||||
|
|
||||||
|
# binding 0.0.0.0 after :: fails on dualstack
|
||||||
|
# but is necessary on non-dualstakc
|
||||||
|
if successful_binds:
|
||||||
|
continue
|
||||||
|
|
||||||
|
raise
|
||||||
|
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
if self.args.ign_ebind or self.args.ign_ebind_all:
|
if self.args.ign_ebind or self.args.ign_ebind_all:
|
||||||
t = "could not listen on {}:{}: {}"
|
t = "could not listen on {}:{}: {}"
|
||||||
self.log("tcpsrv", t.format(ip, port, ex), c=3)
|
self.log("tcpsrv", t.format(ip, port, ex), c=3)
|
||||||
|
pad = True
|
||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
if not self.srv and not self.args.ign_ebind_all:
|
if not self.srv and not self.args.ign_ebind_all:
|
||||||
raise Exception("could not listen on any of the given interfaces")
|
raise Exception("could not listen on any of the given interfaces")
|
||||||
|
|
||||||
if self.nsrv != len(self.srv):
|
if pad:
|
||||||
self.log("tcpsrv", "")
|
self.log("tcpsrv", "")
|
||||||
|
|
||||||
ip = "127.0.0.1"
|
ip = "127.0.0.1"
|
||||||
|
@ -81,7 +114,11 @@ class TcpSrv(object):
|
||||||
t = "available @ {}://{}:{}/ (\033[33m{}\033[0m)"
|
t = "available @ {}://{}:{}/ (\033[33m{}\033[0m)"
|
||||||
for ip, desc in sorted(eps.items(), key=lambda x: x[1]):
|
for ip, desc in sorted(eps.items(), key=lambda x: x[1]):
|
||||||
for port in sorted(self.args.p):
|
for port in sorted(self.args.p):
|
||||||
if port not in ok.get(ip, ok.get("0.0.0.0", [])):
|
if (
|
||||||
|
port not in ok.get(ip, [])
|
||||||
|
and port not in ok.get("::", [])
|
||||||
|
and port not in ok.get("0.0.0.0", [])
|
||||||
|
):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
proto = " http"
|
proto = " http"
|
||||||
|
@ -90,7 +127,8 @@ class TcpSrv(object):
|
||||||
elif self.args.https_only or port == 443:
|
elif self.args.https_only or port == 443:
|
||||||
proto = "https"
|
proto = "https"
|
||||||
|
|
||||||
msgs.append(t.format(proto, ip, port, desc))
|
hip = "[{}]".format(ip) if ":" in ip else ip
|
||||||
|
msgs.append(t.format(proto, hip, port, desc))
|
||||||
|
|
||||||
is_ext = "external" in unicode(desc)
|
is_ext = "external" in unicode(desc)
|
||||||
qrt = qr1 if is_ext else qr2
|
qrt = qr1 if is_ext else qr2
|
||||||
|
@ -125,18 +163,20 @@ class TcpSrv(object):
|
||||||
title_tab[tk] = {tv: 1}
|
title_tab[tk] = {tv: 1}
|
||||||
|
|
||||||
if msgs:
|
if msgs:
|
||||||
msgs[-1] += "\n"
|
|
||||||
for t in msgs:
|
for t in msgs:
|
||||||
self.log("tcpsrv", t)
|
self.log("tcpsrv", t)
|
||||||
|
|
||||||
if self.args.wintitle:
|
if self.args.wintitle:
|
||||||
self._set_wintitle(title_tab)
|
self._set_wintitle(title_tab)
|
||||||
|
else:
|
||||||
|
print("\n", end="")
|
||||||
|
|
||||||
if self.args.qr or self.args.qrs:
|
if self.args.qr or self.args.qrs:
|
||||||
self.qr = self._qr(qr1, qr2)
|
self.qr = self._qr(qr1, qr2)
|
||||||
|
|
||||||
def _listen(self, ip: str, port: int) -> None:
|
def _listen(self, ip: str, port: int) -> None:
|
||||||
srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
ipv = socket.AF_INET6 if ":" in ip else socket.AF_INET
|
||||||
|
srv = socket.socket(ipv, socket.SOCK_STREAM)
|
||||||
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
srv.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
srv.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
||||||
srv.settimeout(None) # < does not inherit, ^ does
|
srv.settimeout(None) # < does not inherit, ^ does
|
||||||
|
@ -153,17 +193,40 @@ class TcpSrv(object):
|
||||||
raise Exception(e)
|
raise Exception(e)
|
||||||
|
|
||||||
def run(self) -> None:
|
def run(self) -> None:
|
||||||
|
all_eps = [x.getsockname()[:2] for x in self.srv]
|
||||||
|
bound = []
|
||||||
|
srvs = []
|
||||||
for srv in self.srv:
|
for srv in self.srv:
|
||||||
srv.listen(self.args.nc)
|
ip, port = srv.getsockname()[:2]
|
||||||
ip, port = srv.getsockname()
|
try:
|
||||||
|
srv.listen(self.args.nc)
|
||||||
|
except:
|
||||||
|
if ip == "0.0.0.0" and ("::", port) in bound:
|
||||||
|
# dualstack
|
||||||
|
srv.close()
|
||||||
|
continue
|
||||||
|
|
||||||
|
if ip == "::" and ("0.0.0.0", port) in all_eps:
|
||||||
|
# no ipv6
|
||||||
|
srv.close()
|
||||||
|
continue
|
||||||
|
|
||||||
|
raise
|
||||||
|
|
||||||
|
bound.append((ip, port))
|
||||||
|
srvs.append(srv)
|
||||||
fno = srv.fileno()
|
fno = srv.fileno()
|
||||||
msg = "listening @ {}:{} f{} p{}".format(ip, port, fno, os.getpid())
|
hip = "[{}]".format(ip) if ":" in ip else ip
|
||||||
|
msg = "listening @ {}:{} f{} p{}".format(hip, port, fno, os.getpid())
|
||||||
self.log("tcpsrv", msg)
|
self.log("tcpsrv", msg)
|
||||||
if self.args.q:
|
if self.args.q:
|
||||||
print(msg)
|
print(msg)
|
||||||
|
|
||||||
self.hub.broker.say("listen", srv)
|
self.hub.broker.say("listen", srv)
|
||||||
|
|
||||||
|
self.srv = srvs
|
||||||
|
self.nsrv = len(srvs)
|
||||||
|
|
||||||
def shutdown(self) -> None:
|
def shutdown(self) -> None:
|
||||||
self.stopping = True
|
self.stopping = True
|
||||||
try:
|
try:
|
||||||
|
@ -209,19 +272,23 @@ class TcpSrv(object):
|
||||||
except:
|
except:
|
||||||
return self.ips_linux_ifconfig()
|
return self.ips_linux_ifconfig()
|
||||||
|
|
||||||
r = re.compile(r"^\s+inet ([^ ]+)/.* (.*)")
|
r = re.compile(r"^\s+inet6? ([^ ]+)/")
|
||||||
ri = re.compile(r"^\s*[0-9]+\s*:.*")
|
ri = re.compile(r"^[0-9]+: ([^:]+): ")
|
||||||
|
dev = ""
|
||||||
up = False
|
up = False
|
||||||
eps: dict[str, str] = {}
|
eps: dict[str, str] = {}
|
||||||
for ln in txt.split("\n"):
|
for ln in txt.split("\n"):
|
||||||
if ri.match(ln):
|
m = ri.match(ln)
|
||||||
|
if m:
|
||||||
|
dev = m.group(1)
|
||||||
up = "UP" in re.split("[>,< ]", ln)
|
up = "UP" in re.split("[>,< ]", ln)
|
||||||
|
|
||||||
try:
|
m = r.match(ln.rstrip())
|
||||||
ip, dev = r.match(ln.rstrip()).groups() # type: ignore
|
if not m or not dev or " scope link" in ln:
|
||||||
eps[ip] = dev + ("" if up else ", \033[31mLINK-DOWN")
|
continue
|
||||||
except:
|
|
||||||
pass
|
ip = m.group(1)
|
||||||
|
eps[ip] = dev + ("" if up else ", \033[31mLINK-DOWN")
|
||||||
|
|
||||||
return eps
|
return eps
|
||||||
|
|
||||||
|
@ -314,7 +381,7 @@ class TcpSrv(object):
|
||||||
else:
|
else:
|
||||||
eps = self.ips_linux()
|
eps = self.ips_linux()
|
||||||
|
|
||||||
if "0.0.0.0" not in listen_ips:
|
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 in listen_ips}
|
eps = {k: v for k, v in eps.items() if k in listen_ips}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -323,14 +390,15 @@ class TcpSrv(object):
|
||||||
if not ext_ips:
|
if not ext_ips:
|
||||||
raise Exception()
|
raise Exception()
|
||||||
except:
|
except:
|
||||||
ext_ips = [self._defroute()]
|
rt = self._defroute()
|
||||||
|
ext_ips = [rt] if rt else []
|
||||||
|
|
||||||
for lip in listen_ips:
|
for lip in listen_ips:
|
||||||
if not ext_ips or lip not in ["0.0.0.0"] + ext_ips:
|
if not ext_ips or lip not in ["0.0.0.0", "::"] + ext_ips:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
desc = "\033[32mexternal"
|
desc = "\033[32mexternal"
|
||||||
ips = ext_ips if lip == "0.0.0.0" else [lip]
|
ips = ext_ips if lip in ["0.0.0.0", "::"] else [lip]
|
||||||
for ip in ips:
|
for ip in ips:
|
||||||
try:
|
try:
|
||||||
if "external" not in eps[ip]:
|
if "external" not in eps[ip]:
|
||||||
|
@ -422,6 +490,9 @@ class TcpSrv(object):
|
||||||
if not ip:
|
if not ip:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
if ":" in ip:
|
||||||
|
ip = "[{}]".format(ip)
|
||||||
|
|
||||||
if self.args.http_only:
|
if self.args.http_only:
|
||||||
https = ""
|
https = ""
|
||||||
elif self.args.https_only:
|
elif self.args.https_only:
|
||||||
|
|
Loading…
Reference in a new issue