mirror of
https://github.com/3proxy/3proxy.git
synced 2026-08-05 17:20:11 +08:00
fix: continue parent negotiation after PROXY header
After sending an HAProxy PROXY v1 header for a chained parent, keep negotiating the real parent protocol instead of returning early. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
305f9932ac
commit
af5e6f3a4d
@ -1472,16 +1472,19 @@ socks -H -p1080
|
||||
<p>
|
||||
<b>Sending PROXY protocol header to parent proxy:</b>
|
||||
<br>Use the <code>ha</code> parent type to send a PROXY protocol v1 header to
|
||||
the parent proxy. This must be the last parent in the chain:
|
||||
the parent proxy. Place <code>ha</code> before the parent that should receive
|
||||
the header; after the header is sent, parent protocol negotiation continues:
|
||||
</p><pre>
|
||||
allow *
|
||||
parent 1000 ha
|
||||
parent 1000 ha 0.0.0.0 0
|
||||
parent 1000 socks5 parent.example.com 1080
|
||||
socks
|
||||
proxy
|
||||
</pre>
|
||||
<p>
|
||||
This configuration sends the client IP information to the SOCKS5 parent proxy
|
||||
via the PROXY protocol.
|
||||
This configuration connects to the SOCKS5 parent, sends the PROXY protocol
|
||||
header with the original client IP, then performs SOCKS5 negotiation before
|
||||
relaying the client payload. The parent should use <code>-H</code> to accept
|
||||
the header (for example <code>socks -H</code>).
|
||||
</p>
|
||||
<li><a name="MAXSEG"><i>How to set TCP maximum segment size (MSS)</i></a>
|
||||
<p>
|
||||
|
||||
@ -1531,16 +1531,20 @@ socks -H -p1080
|
||||
<p>
|
||||
<b>Отправка заголовка PROXY протокола родительскому прокси:</b>
|
||||
<br>Используйте тип родительского прокси <code>ha</code> для отправки заголовка
|
||||
PROXY протокола v1 родительскому прокси. Это должен быть последний родитель в цепочке:
|
||||
PROXY протокола v1 родительскому прокси. Укажите <code>ha</code> перед родителем,
|
||||
который должен получить заголовок; после отправки заголовка переговоры по протоколу
|
||||
родителя продолжаются:
|
||||
</p><pre>
|
||||
allow *
|
||||
parent 1000 ha
|
||||
parent 1000 ha 0.0.0.0 0
|
||||
parent 1000 socks5 parent.example.com 1080
|
||||
socks
|
||||
proxy
|
||||
</pre>
|
||||
<p>
|
||||
Эта конфигурация отправляет информацию об IP-адресе клиента SOCKS5 родительскому
|
||||
прокси через PROXY протокол.
|
||||
Эта конфигурация подключается к SOCKS5 родителю, отправляет PROXY заголовок
|
||||
с исходным IP клиента, затем выполняет SOCKS5-переговоры и только после этого
|
||||
передаёт полезную нагрузку. Родитель должен использовать <code>-H</code>
|
||||
(например <code>socks -H</code>).
|
||||
</p>
|
||||
<li><a name="MAXSEG"><i>Как установить максимальный размер сегмента TCP (MSS)</i></a>
|
||||
<p>
|
||||
|
||||
@ -759,10 +759,15 @@ useful). Never use this option unless you know exactly you
|
||||
need it. <b><br>
|
||||
admin</b> redirect request to local ´admin´
|
||||
service (with -s parameter). <b><br>
|
||||
ha</b> send HAProxy PROXY protocol v1 header to parent
|
||||
proxy. Must be the last in the proxy chain. Useful for
|
||||
passing client IP information to the parent proxy. Example:
|
||||
parent 1000 ha <br>
|
||||
ha</b> send HAProxy PROXY protocol v1 header to the next
|
||||
parent proxy (or to the destination if <b>ha</b> is used
|
||||
alone). Place <b>ha</b> before the parent that should
|
||||
receive the header; after the header is sent, negotiation
|
||||
of that parent protocol continues (SOCKS, CONNECT, etc.).
|
||||
Useful for passing client IP information to the parent
|
||||
proxy. Example: <br>
|
||||
parent 1000 ha 0.0.0.0 0 <br>
|
||||
parent 1000 socks5 parent.example.com 1080 <br>
|
||||
Use "+" proxy only with <b>fakeresolve</b> option
|
||||
<br>
|
||||
Any parent type above can be suffixed with <b>s</b> (e.g.
|
||||
|
||||
@ -828,9 +828,16 @@ unless you know exactly you need it.
|
||||
.br
|
||||
\fBadmin\fR redirect request to local \'admin\' service (with -s parameter).
|
||||
.br
|
||||
\fBha\fR send HAProxy PROXY protocol v1 header to parent proxy. Must be the last
|
||||
in the proxy chain. Useful for passing client IP information to the parent proxy.
|
||||
Example: parent 1000 ha
|
||||
\fBha\fR send HAProxy PROXY protocol v1 header to the next parent proxy (or to
|
||||
the destination if \fBha\fR is used alone). Place \fBha\fR before the parent
|
||||
that should receive the header; after the header is sent, negotiation of that
|
||||
parent protocol continues (SOCKS, CONNECT, etc.). Useful for passing client IP
|
||||
information to the parent proxy.
|
||||
Example:
|
||||
.br
|
||||
parent 1000 ha 0.0.0.0 0
|
||||
.br
|
||||
parent 1000 socks5 parent.example.com 1080
|
||||
.br
|
||||
Use "+" proxy only with \fBfakeresolve\fR option
|
||||
.br
|
||||
|
||||
@ -375,7 +375,9 @@ int handleredirect(struct clientparam * param, struct ace * acentry){
|
||||
ntohs(*SAPORT(¶m->sincl))
|
||||
);
|
||||
if(socksend(param, param->remsock, (unsigned char *)buf, len, conf.timeouts[CHAIN_TO])!=len) return 39;
|
||||
return 0;
|
||||
/* ha alone: PROXY header then plain TCP relay.
|
||||
ha before another parent: continue negotiating that parent. */
|
||||
if(cur->type == R_HA) return 0;
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
430
tests/ha-parent-chain/run.sh
Executable file
430
tests/ha-parent-chain/run.sh
Executable file
@ -0,0 +1,430 @@
|
||||
#!/bin/sh
|
||||
# Regression: HTTP -> ha + SOCKS5 parent -> socks -H must negotiate SOCKS
|
||||
# after the PROXY v1 header and preserve the original client address.
|
||||
#
|
||||
# Topology:
|
||||
# curl -> proxy1 HTTP :18628
|
||||
# parent ha 0.0.0.0 0
|
||||
# parent socks5 127.0.0.1:18183
|
||||
# -> proxy2 SOCKS -H :18183 -> HTTPS dest :19443
|
||||
#
|
||||
# Also runs a wiretap on :18181 to assert byte order:
|
||||
# PROXY -> SOCKS5 greeting/auth/CONNECT -> payload
|
||||
# and a no-HA baseline via SOCKS without -H on :18184.
|
||||
#
|
||||
# Usage:
|
||||
# ./tests/ha-parent-chain/run.sh [path-to-3proxy]
|
||||
# Default binary: ../../bin/3proxy relative to this script.
|
||||
|
||||
set -eu
|
||||
|
||||
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||
ROOT_DIR=$(CDPATH= cd -- "$SCRIPT_DIR/../.." && pwd)
|
||||
PROXYBIN=${1:-"$ROOT_DIR/bin/3proxy"}
|
||||
WORKDIR=${TMPDIR:-/tmp}/3proxy-ha-parent-chain-$$
|
||||
PROXY1_IP=127.0.0.1
|
||||
# Prefer a distinct loopback client address so proxy2's restored IP
|
||||
# cannot be confused with proxy1's TCP peer. Fall back to 127.0.0.1.
|
||||
CLIENT_IP=127.0.0.1
|
||||
CURL_IFACE_ARGS=
|
||||
|
||||
if [ ! -x "$PROXYBIN" ]; then
|
||||
echo "FAIL: 3proxy binary not found or not executable: $PROXYBIN" >&2
|
||||
echo "Build first, e.g. make -f Makefile.unix or Makefile.FreeBSD" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v python3 >/dev/null 2>&1; then
|
||||
echo "FAIL: python3 is required" >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! command -v curl >/dev/null 2>&1; then
|
||||
echo "FAIL: curl is required" >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! command -v openssl >/dev/null 2>&1; then
|
||||
echo "FAIL: openssl is required" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if python3 - <<'PY'
|
||||
import socket
|
||||
s = socket.socket()
|
||||
try:
|
||||
s.bind(("127.0.0.2", 0))
|
||||
except OSError:
|
||||
raise SystemExit(1)
|
||||
finally:
|
||||
s.close()
|
||||
raise SystemExit(0)
|
||||
PY
|
||||
then
|
||||
CLIENT_IP=127.0.0.2
|
||||
CURL_IFACE_ARGS="--interface 127.0.0.2"
|
||||
fi
|
||||
|
||||
cleanup() {
|
||||
for f in proxy1.pid proxy1b.pid proxy1-noha.pid proxy2.pid proxy2-noha.pid wiretap.pid dest.pid; do
|
||||
if [ -f "$WORKDIR/$f" ]; then
|
||||
kill "$(cat "$WORKDIR/$f")" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
# Best-effort cleanup if pidfiles were not written
|
||||
pkill -f "$WORKDIR" 2>/dev/null || true
|
||||
rm -rf "$WORKDIR"
|
||||
}
|
||||
trap cleanup EXIT INT TERM
|
||||
|
||||
mkdir -p "$WORKDIR"
|
||||
cd "$WORKDIR"
|
||||
|
||||
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 1 -nodes \
|
||||
-subj "/CN=127.0.0.1" >/dev/null 2>&1
|
||||
|
||||
cat > dest_server.py <<'PY'
|
||||
#!/usr/bin/env python3
|
||||
import argparse, ssl
|
||||
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||
|
||||
class H(BaseHTTPRequestHandler):
|
||||
def do_GET(self):
|
||||
body = b"OK-DEST\n"
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Length", str(len(body)))
|
||||
self.send_header("Content-Type", "text/plain")
|
||||
self.end_headers()
|
||||
self.wfile.write(body)
|
||||
def log_message(self, *a):
|
||||
pass
|
||||
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--host", default="127.0.0.1")
|
||||
ap.add_argument("--port", type=int, required=True)
|
||||
ap.add_argument("--cert", required=True)
|
||||
ap.add_argument("--key", required=True)
|
||||
args = ap.parse_args()
|
||||
httpd = HTTPServer((args.host, args.port), H)
|
||||
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
||||
ctx.load_cert_chain(args.cert, args.key)
|
||||
httpd.socket = ctx.wrap_socket(httpd.socket, server_side=True)
|
||||
print("ready", flush=True)
|
||||
httpd.serve_forever()
|
||||
PY
|
||||
|
||||
cat > wiretap_socks.py <<'PY'
|
||||
#!/usr/bin/env python3
|
||||
import argparse, socket, struct, threading, sys
|
||||
|
||||
def recv_exact(s, n):
|
||||
buf = b""
|
||||
while len(buf) < n:
|
||||
chunk = s.recv(n - len(buf))
|
||||
if not chunk:
|
||||
raise ConnectionError("eof")
|
||||
buf += chunk
|
||||
return buf
|
||||
|
||||
def handle(conn, addr, logf):
|
||||
order = []
|
||||
client_ip = addr[0]
|
||||
try:
|
||||
line = b""
|
||||
while not line.endswith(b"\n"):
|
||||
b = conn.recv(1)
|
||||
if not b:
|
||||
break
|
||||
line += b
|
||||
if not line.startswith(b"PROXY "):
|
||||
logf.write("ORDER=NO_PROXY FIRST=%r\n" % (line[:32],))
|
||||
logf.flush()
|
||||
return
|
||||
order.append("PROXY")
|
||||
parts = line.decode("latin1", "replace").strip().split()
|
||||
if len(parts) >= 6:
|
||||
client_ip = parts[2]
|
||||
ver = conn.recv(1)
|
||||
if ver != b"\x05":
|
||||
more = ver + conn.recv(16)
|
||||
order.append("PAYLOAD")
|
||||
logf.write("ORDER=%s FIRST_PAYLOAD=%r client_ip=%s FAIL_NO_SOCKS\n" %
|
||||
("->".join(order), more, client_ip))
|
||||
logf.flush()
|
||||
return
|
||||
order.append("SOCKS5")
|
||||
nmethods = conn.recv(1)[0]
|
||||
methods = list(conn.recv(nmethods))
|
||||
if 2 in methods:
|
||||
conn.sendall(b"\x05\x02")
|
||||
auth = recv_exact(conn, 2)
|
||||
user = recv_exact(conn, auth[1])
|
||||
plen = recv_exact(conn, 1)[0]
|
||||
_ = recv_exact(conn, plen)
|
||||
order.append("SOCKS5_AUTH")
|
||||
conn.sendall(b"\x01\x00")
|
||||
logf.write("SOCKS5_AUTH user=%r client_ip=%s\n" % (user, client_ip))
|
||||
elif 0 in methods:
|
||||
conn.sendall(b"\x05\x00")
|
||||
else:
|
||||
conn.sendall(b"\x05\xff")
|
||||
return
|
||||
hdr = recv_exact(conn, 4)
|
||||
atyp = hdr[3]
|
||||
if atyp == 1:
|
||||
host = socket.inet_ntoa(recv_exact(conn, 4))
|
||||
elif atyp == 3:
|
||||
ln = recv_exact(conn, 1)[0]
|
||||
host = recv_exact(conn, ln).decode()
|
||||
elif atyp == 4:
|
||||
host = socket.inet_ntop(socket.AF_INET6, recv_exact(conn, 16))
|
||||
else:
|
||||
return
|
||||
port = struct.unpack("!H", recv_exact(conn, 2))[0]
|
||||
order.append("SOCKS5_CONNECT")
|
||||
rem = socket.create_connection((host, port), timeout=10)
|
||||
conn.sendall(b"\x05\x00\x00\x01\x00\x00\x00\x00\x00\x00")
|
||||
order.append("PAYLOAD")
|
||||
logf.write("ORDER=%s SUCCESS client_ip=%s target=%s:%s\n" %
|
||||
("->".join(order), client_ip, host, port))
|
||||
logf.flush()
|
||||
|
||||
def pump(a, b):
|
||||
try:
|
||||
while True:
|
||||
d = a.recv(65536)
|
||||
if not d:
|
||||
break
|
||||
b.sendall(d)
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
b.shutdown(socket.SHUT_WR)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
t1 = threading.Thread(target=pump, args=(conn, rem), daemon=True)
|
||||
t2 = threading.Thread(target=pump, args=(rem, conn), daemon=True)
|
||||
t1.start(); t2.start(); t1.join(); t2.join()
|
||||
except Exception as e:
|
||||
logf.write("ERROR=%r ORDER=%s\n" % (e, "->".join(order)))
|
||||
logf.flush()
|
||||
finally:
|
||||
try:
|
||||
conn.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--port", type=int, required=True)
|
||||
ap.add_argument("--log", required=True)
|
||||
args = ap.parse_args()
|
||||
logf = open(args.log, "a", buffering=1)
|
||||
srv = socket.socket()
|
||||
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
srv.bind(("127.0.0.1", args.port))
|
||||
srv.listen(50)
|
||||
print("ready", flush=True)
|
||||
while True:
|
||||
c, a = srv.accept()
|
||||
threading.Thread(target=handle, args=(c, a, logf), daemon=True).start()
|
||||
PY
|
||||
|
||||
python3 dest_server.py --port 19443 --cert cert.pem --key key.pem > dest.out 2>&1 &
|
||||
echo $! > dest.pid
|
||||
python3 wiretap_socks.py --port 18181 --log wire.log > wiretap.out 2>&1 &
|
||||
echo $! > wiretap.pid
|
||||
|
||||
# Wait until helpers are listening
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
if grep -q ready dest.out 2>/dev/null && grep -q ready wiretap.out 2>/dev/null; then
|
||||
break
|
||||
fi
|
||||
i=$((i + 1))
|
||||
sleep 0.1
|
||||
done
|
||||
|
||||
# Real SOCKS -H parent. Use iponly here so the test is not coupled to
|
||||
# unrelated cleartext password-table comparison behavior; username/password
|
||||
# SOCKS auth is exercised against the wiretap parent below.
|
||||
cat > proxy2.cfg <<EOF
|
||||
nserver 8.8.8.8
|
||||
log $WORKDIR/proxy2.log
|
||||
logformat "%Y%m%d%H%M%S.%. %p %E %U %C:%c %R:%r %O %I %T"
|
||||
auth iponly
|
||||
allow *
|
||||
socks -H -p18183
|
||||
EOF
|
||||
|
||||
# Baseline SOCKS parent without -H (must not share the -H listener)
|
||||
cat > proxy2-noha.cfg <<EOF
|
||||
nserver 8.8.8.8
|
||||
log $WORKDIR/proxy2-noha.log
|
||||
logformat "%Y%m%d%H%M%S.%. %p %E %U %C:%c %R:%r %O %I %T"
|
||||
auth iponly
|
||||
allow *
|
||||
socks -p18184
|
||||
EOF
|
||||
|
||||
cat > proxy1.cfg <<EOF
|
||||
nserver 8.8.8.8
|
||||
log $WORKDIR/proxy1.log
|
||||
logformat "%Y%m%d%H%M%S.%. %p %E %U %C:%c %R:%r %O %I %T"
|
||||
auth iponly
|
||||
allow *
|
||||
parent 1000 ha 0.0.0.0 0
|
||||
parent 1000 socks5 127.0.0.1 18183
|
||||
proxy -p18628
|
||||
EOF
|
||||
|
||||
# Wire-order probe: same HA chain, parent is the Python wiretap with user/pass
|
||||
cat > proxy1b.cfg <<EOF
|
||||
nserver 8.8.8.8
|
||||
log $WORKDIR/proxy1b.log
|
||||
logformat "%Y%m%d%H%M%S.%. %p %E %U %C:%c %R:%r %O %I %T"
|
||||
auth iponly
|
||||
allow *
|
||||
parent 1000 ha 0.0.0.0 0
|
||||
parent 1000 socks5 127.0.0.1 18181 upstream upstreampassword
|
||||
proxy -p18630
|
||||
EOF
|
||||
|
||||
cat > proxy1-noha.cfg <<EOF
|
||||
nserver 8.8.8.8
|
||||
log $WORKDIR/proxy1-noha.log
|
||||
logformat "%Y%m%d%H%M%S.%. %p %E %U %C:%c %R:%r %O %I %T"
|
||||
auth iponly
|
||||
allow *
|
||||
parent 1000 socks5 127.0.0.1 18184
|
||||
proxy -p18629
|
||||
EOF
|
||||
|
||||
"$PROXYBIN" proxy2.cfg > proxy2.out 2>&1 &
|
||||
echo $! > proxy2.pid
|
||||
"$PROXYBIN" proxy2-noha.cfg > proxy2-noha.out 2>&1 &
|
||||
echo $! > proxy2-noha.pid
|
||||
"$PROXYBIN" proxy1.cfg > proxy1.out 2>&1 &
|
||||
echo $! > proxy1.pid
|
||||
"$PROXYBIN" proxy1b.cfg > proxy1b.out 2>&1 &
|
||||
echo $! > proxy1b.pid
|
||||
"$PROXYBIN" proxy1-noha.cfg > proxy1-noha.out 2>&1 &
|
||||
echo $! > proxy1-noha.pid
|
||||
sleep 0.4
|
||||
|
||||
# shellcheck disable=SC2086
|
||||
NOHA_BODY=$(curl -sk --max-time 15 $CURL_IFACE_ARGS \
|
||||
--proxy "http://${PROXY1_IP}:18629" \
|
||||
"https://127.0.0.1:19443/" || true)
|
||||
|
||||
# shellcheck disable=SC2086
|
||||
BODY=$(curl -sk --max-time 15 $CURL_IFACE_ARGS \
|
||||
--proxy "http://${PROXY1_IP}:18628" \
|
||||
"https://127.0.0.1:19443/" || true)
|
||||
|
||||
# shellcheck disable=SC2086
|
||||
WIRE_BODY=$(curl -sk --max-time 15 $CURL_IFACE_ARGS \
|
||||
--proxy "http://${PROXY1_IP}:18630" \
|
||||
"https://127.0.0.1:19443/" || true)
|
||||
|
||||
# Stop proxies so request logs are flushed to disk
|
||||
for f in proxy1.pid proxy1b.pid proxy1-noha.pid proxy2.pid proxy2-noha.pid; do
|
||||
if [ -f "$f" ]; then
|
||||
kill "$(cat "$f")" 2>/dev/null || true
|
||||
wait "$(cat "$f")" 2>/dev/null || true
|
||||
rm -f "$f"
|
||||
fi
|
||||
done
|
||||
sleep 0.1
|
||||
|
||||
echo "=== proxy1.log ==="
|
||||
cat proxy1.log 2>/dev/null || true
|
||||
echo "=== proxy1-noha.log ==="
|
||||
cat proxy1-noha.log 2>/dev/null || true
|
||||
echo "=== proxy2.log ==="
|
||||
cat proxy2.log 2>/dev/null || true
|
||||
echo "=== proxy2-noha.log ==="
|
||||
cat proxy2-noha.log 2>/dev/null || true
|
||||
echo "=== wire.log ==="
|
||||
cat wire.log 2>/dev/null || true
|
||||
echo "=== curl body (no-HA) ==="
|
||||
printf '%s\n' "$NOHA_BODY"
|
||||
echo "=== curl body (HA -> real proxy2) ==="
|
||||
printf '%s\n' "$BODY"
|
||||
echo "=== curl body (HA -> wiretap) ==="
|
||||
printf '%s\n' "$WIRE_BODY"
|
||||
|
||||
fail=0
|
||||
|
||||
if [ "$NOHA_BODY" != "OK-DEST" ]; then
|
||||
echo "FAIL: baseline HTTPS through socks5 parent (no HA) did not return OK-DEST (got: $NOHA_BODY)" >&2
|
||||
fail=1
|
||||
fi
|
||||
|
||||
if [ "$BODY" != "OK-DEST" ]; then
|
||||
echo "FAIL: HTTPS through ha->socks5->socks -H did not return OK-DEST (got: $BODY)" >&2
|
||||
fail=1
|
||||
fi
|
||||
|
||||
if [ "$WIRE_BODY" != "OK-DEST" ]; then
|
||||
echo "FAIL: HTTPS through ha->wiretap socks did not return OK-DEST (got: $WIRE_BODY)" >&2
|
||||
fail=1
|
||||
fi
|
||||
|
||||
if ! grep -q "ORDER=PROXY->SOCKS5->SOCKS5_AUTH->SOCKS5_CONNECT->PAYLOAD SUCCESS" wire.log; then
|
||||
echo "FAIL: wire order was not PROXY -> SOCKS5 -> AUTH -> CONNECT -> PAYLOAD" >&2
|
||||
fail=1
|
||||
fi
|
||||
|
||||
if ! grep -q "client_ip=$CLIENT_IP" wire.log; then
|
||||
echo "FAIL: PROXY header did not carry original client IP $CLIENT_IP" >&2
|
||||
fail=1
|
||||
fi
|
||||
|
||||
# proxy2 must complete SOCKS CONNECT (not UNKNOWN) and reach the HTTPS destination
|
||||
if ! grep -E " 00000 +- +${CLIENT_IP}:[0-9]+ +127\\.0\\.0\\.1:19443 " proxy2.log >/dev/null; then
|
||||
echo "FAIL: proxy2 did not complete SOCKS CONNECT from restored client $CLIENT_IP to :19443" >&2
|
||||
fail=1
|
||||
fi
|
||||
|
||||
if grep -E " 0040[0-9] +- +${CLIENT_IP}:.*UNKNOWN" proxy2.log >/dev/null; then
|
||||
echo "FAIL: proxy2 still saw UNKNOWN after PROXY (parent negotiation skipped)" >&2
|
||||
fail=1
|
||||
fi
|
||||
|
||||
# Wiretap must have completed username/password SOCKS5 authentication
|
||||
if ! grep -q "SOCKS5_AUTH user=b'upstream'" wire.log; then
|
||||
echo "FAIL: SOCKS5 username/password authentication was not completed" >&2
|
||||
fail=1
|
||||
fi
|
||||
|
||||
# PROXY restoration: proxy2's logged client port must match proxy1's client port.
|
||||
# The TCP peer port on proxy2 would be proxy1's outbound ephemeral port instead.
|
||||
CLIENT_PORT=$(awk '/CONNECT 127\.0\.0\.1:19443/ && / 00000 / {
|
||||
for (i = 1; i <= NF; i++) if ($i ~ /^'"$CLIENT_IP"':[0-9]+$/) {
|
||||
split($i, a, ":"); print a[2]; exit
|
||||
}
|
||||
}' proxy1.log)
|
||||
PROXY2_PORT=$(awk '/CONNECT 127\.0\.0\.1:19443/ && / 00000 / {
|
||||
for (i = 1; i <= NF; i++) if ($i ~ /^'"$CLIENT_IP"':[0-9]+$/) {
|
||||
split($i, a, ":"); print a[2]; exit
|
||||
}
|
||||
}' proxy2.log)
|
||||
if [ -z "$CLIENT_PORT" ] || [ -z "$PROXY2_PORT" ] || [ "$CLIENT_PORT" != "$PROXY2_PORT" ]; then
|
||||
echo "FAIL: proxy2 client port ($PROXY2_PORT) does not match original client port on proxy1 ($CLIENT_PORT)" >&2
|
||||
fail=1
|
||||
fi
|
||||
|
||||
# When a distinct client IP is available, restored address must not be proxy1's peer alone
|
||||
if [ "$CLIENT_IP" != "127.0.0.1" ]; then
|
||||
if grep -E " 00000 +- +127\\.0\\.0\\.1:[0-9]+ +127\\.0\\.0\\.1:19443 " proxy2.log >/dev/null && \
|
||||
! grep -E " 00000 +- +${CLIENT_IP}:" proxy2.log >/dev/null; then
|
||||
echo "FAIL: proxy2 logged proxy1 address instead of original client $CLIENT_IP" >&2
|
||||
fail=1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$fail" -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "PASS: ha parent chaining sends PROXY then completes SOCKS5 negotiation (client_ip=$CLIENT_IP)"
|
||||
exit 0
|
||||
Loading…
Reference in New Issue
Block a user