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
|
||||
|
||||
* backend stuff
|
||||
* ☑ IPv6
|
||||
* ☑ [multiprocessing](#performance) (actual multithreading)
|
||||
* ☑ volumes (mountpoints)
|
||||
* ☑ [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 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
|
||||
|
||||
|
@ -756,7 +760,7 @@ some **BIG WARNINGS** specific to SMB/CIFS, in decreasing importance:
|
|||
and some minor issues,
|
||||
* 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
|
||||
* 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
|
||||
* 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
|
||||
|
|
|
@ -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 = 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("--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")
|
||||
|
|
|
@ -1366,7 +1366,7 @@ class HttpCli(object):
|
|||
|
||||
url = "{}://{}/{}".format(
|
||||
"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,
|
||||
)
|
||||
|
||||
|
@ -1386,7 +1386,7 @@ class HttpCli(object):
|
|||
else:
|
||||
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)
|
||||
return True
|
||||
|
||||
|
@ -2037,7 +2037,7 @@ class HttpCli(object):
|
|||
"url": "{}://{}/{}".format(
|
||||
"https" if self.is_https else "http",
|
||||
self.headers.get("host")
|
||||
or "{}:{}".format(*list(self.s.getsockname())),
|
||||
or "{}:{}".format(*list(self.s.getsockname()[:2])),
|
||||
rel_url,
|
||||
),
|
||||
"sha512": sha_hex[:56],
|
||||
|
|
|
@ -174,7 +174,7 @@ class HttpSrv(object):
|
|||
sck.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
||||
sck.settimeout(None) # < does not inherit, ^ does
|
||||
|
||||
ip, port = sck.getsockname()
|
||||
ip, port = sck.getsockname()[:2]
|
||||
self.srvs.append(sck)
|
||||
self.nclimax = math.ceil(self.args.nc * 1.0 / nlisteners)
|
||||
Daemon(
|
||||
|
@ -185,9 +185,10 @@ class HttpSrv(object):
|
|||
|
||||
def thr_listen(self, srv_sck: socket.socket) -> None:
|
||||
"""listens on a shared tcp server"""
|
||||
ip, port = srv_sck.getsockname()
|
||||
ip, port = srv_sck.getsockname()[:2]
|
||||
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)
|
||||
|
||||
def fun() -> None:
|
||||
|
@ -261,7 +262,12 @@ class HttpSrv(object):
|
|||
self.log(self.name, "|%sC-acc1" % ("-" * 2,), c="90")
|
||||
|
||||
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:
|
||||
self.log(self.name, "accept({}): {}".format(fno, ex), c=6)
|
||||
time.sleep(0.02)
|
||||
|
|
|
@ -75,7 +75,11 @@ class SMB(object):
|
|||
smbserver.isInFileJail = self._is_in_file_jail
|
||||
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)
|
||||
srv = smbserver.SimpleSMBServer(listenAddress=ip, listenPort=port)
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ from .util import (
|
|||
E_ADDR_NOT_AVAIL,
|
||||
E_UNREACH,
|
||||
chkcmd,
|
||||
min_ex,
|
||||
sunpack,
|
||||
termsize,
|
||||
)
|
||||
|
@ -43,25 +44,57 @@ class TcpSrv(object):
|
|||
self.srv: list[socket.socket] = []
|
||||
self.nsrv = 0
|
||||
self.qr = ""
|
||||
pad = False
|
||||
ok: dict[str, list[int]] = {}
|
||||
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:
|
||||
self.nsrv += 1
|
||||
successful_binds = 0
|
||||
try:
|
||||
self._listen(ip, port)
|
||||
ok[ip].append(port)
|
||||
for ipa in ips:
|
||||
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:
|
||||
if self.args.ign_ebind or self.args.ign_ebind_all:
|
||||
t = "could not listen on {}:{}: {}"
|
||||
self.log("tcpsrv", t.format(ip, port, ex), c=3)
|
||||
pad = True
|
||||
else:
|
||||
raise
|
||||
|
||||
if not self.srv and not self.args.ign_ebind_all:
|
||||
raise Exception("could not listen on any of the given interfaces")
|
||||
|
||||
if self.nsrv != len(self.srv):
|
||||
if pad:
|
||||
self.log("tcpsrv", "")
|
||||
|
||||
ip = "127.0.0.1"
|
||||
|
@ -81,7 +114,11 @@ class TcpSrv(object):
|
|||
t = "available @ {}://{}:{}/ (\033[33m{}\033[0m)"
|
||||
for ip, desc in sorted(eps.items(), key=lambda x: x[1]):
|
||||
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
|
||||
|
||||
proto = " http"
|
||||
|
@ -90,7 +127,8 @@ class TcpSrv(object):
|
|||
elif self.args.https_only or port == 443:
|
||||
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)
|
||||
qrt = qr1 if is_ext else qr2
|
||||
|
@ -125,18 +163,20 @@ class TcpSrv(object):
|
|||
title_tab[tk] = {tv: 1}
|
||||
|
||||
if msgs:
|
||||
msgs[-1] += "\n"
|
||||
for t in msgs:
|
||||
self.log("tcpsrv", t)
|
||||
|
||||
if self.args.wintitle:
|
||||
self._set_wintitle(title_tab)
|
||||
else:
|
||||
print("\n", end="")
|
||||
|
||||
if self.args.qr or self.args.qrs:
|
||||
self.qr = self._qr(qr1, qr2)
|
||||
|
||||
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.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
||||
srv.settimeout(None) # < does not inherit, ^ does
|
||||
|
@ -153,17 +193,40 @@ class TcpSrv(object):
|
|||
raise Exception(e)
|
||||
|
||||
def run(self) -> None:
|
||||
all_eps = [x.getsockname()[:2] for x in self.srv]
|
||||
bound = []
|
||||
srvs = []
|
||||
for srv in self.srv:
|
||||
ip, port = srv.getsockname()[:2]
|
||||
try:
|
||||
srv.listen(self.args.nc)
|
||||
ip, port = srv.getsockname()
|
||||
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()
|
||||
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)
|
||||
if self.args.q:
|
||||
print(msg)
|
||||
|
||||
self.hub.broker.say("listen", srv)
|
||||
|
||||
self.srv = srvs
|
||||
self.nsrv = len(srvs)
|
||||
|
||||
def shutdown(self) -> None:
|
||||
self.stopping = True
|
||||
try:
|
||||
|
@ -209,19 +272,23 @@ class TcpSrv(object):
|
|||
except:
|
||||
return self.ips_linux_ifconfig()
|
||||
|
||||
r = re.compile(r"^\s+inet ([^ ]+)/.* (.*)")
|
||||
ri = re.compile(r"^\s*[0-9]+\s*:.*")
|
||||
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"):
|
||||
if ri.match(ln):
|
||||
m = ri.match(ln)
|
||||
if m:
|
||||
dev = m.group(1)
|
||||
up = "UP" in re.split("[>,< ]", ln)
|
||||
|
||||
try:
|
||||
ip, dev = r.match(ln.rstrip()).groups() # type: ignore
|
||||
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")
|
||||
except:
|
||||
pass
|
||||
|
||||
return eps
|
||||
|
||||
|
@ -314,7 +381,7 @@ class TcpSrv(object):
|
|||
else:
|
||||
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}
|
||||
|
||||
try:
|
||||
|
@ -323,14 +390,15 @@ class TcpSrv(object):
|
|||
if not ext_ips:
|
||||
raise Exception()
|
||||
except:
|
||||
ext_ips = [self._defroute()]
|
||||
rt = self._defroute()
|
||||
ext_ips = [rt] if rt else []
|
||||
|
||||
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
|
||||
|
||||
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:
|
||||
try:
|
||||
if "external" not in eps[ip]:
|
||||
|
@ -422,6 +490,9 @@ class TcpSrv(object):
|
|||
if not ip:
|
||||
return ""
|
||||
|
||||
if ":" in ip:
|
||||
ip = "[{}]".format(ip)
|
||||
|
||||
if self.args.http_only:
|
||||
https = ""
|
||||
elif self.args.https_only:
|
||||
|
|
Loading…
Reference in a new issue