mirror of
https://github.com/3proxy/3proxy.git
synced 2026-09-29 16:55:51 +08:00
Compare commits
8 Commits
16ac797008
...
ca4667c035
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ca4667c035 | ||
|
|
41a08c11be | ||
|
|
6c4663a045 | ||
|
|
83139bc2e8 | ||
|
|
2dc4a422c5 | ||
|
|
d0ec7687db | ||
|
|
b8e5853b6b | ||
|
|
ad6e151b7c |
2
.github/workflows/c-cpp-Linux.yml
vendored
2
.github/workflows/c-cpp-Linux.yml
vendored
@ -27,7 +27,7 @@ jobs:
|
||||
if: ${{ startsWith(matrix.target, 'ubuntu') }}
|
||||
run: sudo apt-get update && sudo apt-get install -y libssl-dev libpam-dev libpcre2-dev
|
||||
- name: make
|
||||
run: make -f Makefile.Linux
|
||||
run: make -f Makefile.Linux MAILPROXY=true FTP=true
|
||||
- name: regression tests
|
||||
run: python3 tests/run.py
|
||||
- name: mkdir
|
||||
|
||||
85
.github/workflows/release-tarball.yml
vendored
Normal file
85
.github/workflows/release-tarball.yml
vendored
Normal file
@ -0,0 +1,85 @@
|
||||
name: Release source tarball
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
tarball:
|
||||
permissions:
|
||||
contents: write
|
||||
id-token: write
|
||||
attestations: write
|
||||
name: "source tarball"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
- name: env
|
||||
run: |
|
||||
if [ -f RELEASE ]; then
|
||||
RELEASE=$(tr -d ' \t\r\n' < RELEASE)
|
||||
else
|
||||
RELEASE=$(tr -d ' \t\r\n' < DEVEL)
|
||||
fi
|
||||
echo "RELEASE=$RELEASE" >> $GITHUB_ENV
|
||||
|
||||
- name: Create tarball
|
||||
run: |
|
||||
# git archive is reproducible from the tag: anyone can regenerate the
|
||||
# tarball and compare it against the published checksum.
|
||||
git archive --format=tar.gz -9 \
|
||||
--prefix="3proxy-${{ env.RELEASE }}/" \
|
||||
-o "3proxy-${{ env.RELEASE }}.tar.gz" HEAD
|
||||
tar tzf "3proxy-${{ env.RELEASE }}.tar.gz" >/dev/null
|
||||
ls -l *.tar.gz
|
||||
|
||||
- name: Get artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: "3proxy-${{ env.RELEASE }}-src"
|
||||
path: "*.tar.gz"
|
||||
|
||||
- name: Import signing key
|
||||
if: github.event_name == 'release'
|
||||
env:
|
||||
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
run: |
|
||||
if [ -z "$GPG_PRIVATE_KEY" ]; then echo "GPG_PRIVATE_KEY is not set"; exit 1; fi
|
||||
mkdir -p ~/.gnupg && chmod 700 ~/.gnupg
|
||||
printf 'allow-loopback-pinentry\n' > ~/.gnupg/gpg-agent.conf
|
||||
gpgconf --kill gpg-agent || true
|
||||
printf '%s' "$GPG_PRIVATE_KEY" | gpg --batch --import
|
||||
KEYID=$(gpg --list-secret-keys --with-colons | awk -F: '/^sec:/{print $5; exit}')
|
||||
echo "GPG_KEYID=$KEYID" >> $GITHUB_ENV
|
||||
|
||||
- name: Checksums and detached signatures
|
||||
if: github.event_name == 'release'
|
||||
env:
|
||||
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
|
||||
run: |
|
||||
sha256sum *.tar.gz > SHA256SUMS-src
|
||||
for f in *.tar.gz SHA256SUMS-src; do
|
||||
gpg --batch --yes --pinentry-mode loopback --passphrase "$GPG_PASSPHRASE" \
|
||||
-u "$GPG_KEYID" --armor --detach-sign "$f"
|
||||
done
|
||||
sha256sum -c SHA256SUMS-src
|
||||
gpg --verify SHA256SUMS-src.asc SHA256SUMS-src
|
||||
|
||||
- name: Attest build provenance
|
||||
if: github.event_name == 'release'
|
||||
uses: actions/attest-build-provenance@v4
|
||||
with:
|
||||
subject-path: |
|
||||
*.tar.gz
|
||||
|
||||
- name: Upload to release
|
||||
if: github.event_name == 'release'
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
TAG: ${{ github.event.release.tag_name }}
|
||||
run: gh release upload "$TAG" *.tar.gz *.tar.gz.asc SHA256SUMS-src SHA256SUMS-src.asc
|
||||
@ -70,6 +70,8 @@ option(3PROXY_USE_NETFILTER "Enable Linux netfilter support (Linux only)" ON)
|
||||
option(3PROXY_USE_TRANSPARENT "Build transparent proxying support (Linux and BSD only)" ON)
|
||||
option(3PROXY_USE_UNIX_SOCKETS "Enable Unix domain socket support (Unix only)" ON)
|
||||
option(3PROXY_USE_HTTPSRV "Build the HTTP server and the admin interface on top of it" ON)
|
||||
option(3PROXY_USE_MAILPROXY "Build the pop3p, imapp and smtpp proxies" OFF)
|
||||
option(3PROXY_USE_FTP "Build FTP support: the ftppr service and ftp:// in the HTTP proxy" OFF)
|
||||
|
||||
if(NOT WIN32 AND NOT APPLE)
|
||||
option(3PROXY_STATIC_LINK "Statically link libraries using -Wl,-Bstatic (Linux/Unix only)" OFF)
|
||||
@ -83,10 +85,13 @@ set(3PROXY_BINARY_PREFIX "3proxy_" CACHE STRING "Prefix for standalone module an
|
||||
option(3PROXY_BUILD_NONE "Do not build standalone binaries" OFF)
|
||||
option(3PROXY_BUILD_PROXY "Build standalone proxy binary" ON)
|
||||
option(3PROXY_BUILD_SOCKS "Build standalone socks binary" ON)
|
||||
option(3PROXY_BUILD_POP3P "Build standalone pop3p binary" ON)
|
||||
option(3PROXY_BUILD_IMAPP "Build standalone imapp binary" ON)
|
||||
option(3PROXY_BUILD_SMTPP "Build standalone smtpp binary" ON)
|
||||
option(3PROXY_BUILD_FTPPR "Build standalone ftppr binary" ON)
|
||||
# The mail proxies and FTP are asked for rather than assumed: without them
|
||||
# pop3p, imapp and smtpp are the STARTTLS proxy under those names, and ftp
|
||||
# is not spoken at all. A standalone binary needs the support it is made of.
|
||||
option(3PROXY_BUILD_POP3P "Build standalone pop3p binary" OFF)
|
||||
option(3PROXY_BUILD_IMAPP "Build standalone imapp binary" OFF)
|
||||
option(3PROXY_BUILD_SMTPP "Build standalone smtpp binary" OFF)
|
||||
option(3PROXY_BUILD_FTPPR "Build standalone ftppr binary" OFF)
|
||||
option(3PROXY_BUILD_TCPPM "Build standalone tcppm binary" ON)
|
||||
option(3PROXY_BUILD_UDPPM "Build standalone udppm binary" ON)
|
||||
option(3PROXY_BUILD_TLSPR "Build standalone tlspr binary" ON)
|
||||
@ -250,6 +255,14 @@ else()
|
||||
)
|
||||
endif()
|
||||
|
||||
if(3PROXY_USE_MAILPROXY)
|
||||
add_compile_definitions(WITH_POP3P WITH_IMAPP WITH_SMTPP)
|
||||
endif()
|
||||
|
||||
if(3PROXY_USE_FTP)
|
||||
add_compile_definitions(WITH_FTP)
|
||||
endif()
|
||||
|
||||
if(3PROXY_USE_HTTPSRV)
|
||||
add_compile_definitions(WITH_HTTPSRV)
|
||||
endif()
|
||||
@ -684,6 +697,15 @@ foreach(PROXY_NAME proxy socks pop3p imapp smtpp ftppr tcppm udppm tlspr)
|
||||
continue()
|
||||
endif()
|
||||
|
||||
# A binary of nothing: without the support built, these files hold the
|
||||
# stand-in the main binary uses and no service of their own.
|
||||
if(NOT 3PROXY_USE_MAILPROXY AND PROXY_NAME MATCHES "^(pop3p|imapp|smtpp)$")
|
||||
continue()
|
||||
endif()
|
||||
if(NOT 3PROXY_USE_FTP AND PROXY_NAME STREQUAL "ftppr")
|
||||
continue()
|
||||
endif()
|
||||
|
||||
if(PROXY_NAME STREQUAL "ftppr" OR PROXY_NAME STREQUAL "proxy")
|
||||
# ftppr and proxy use ftp_obj
|
||||
add_executable(${PROXY_NAME}
|
||||
|
||||
@ -24,6 +24,16 @@ LDFLAGS += $(EXTRA_LDFLAGS)
|
||||
# -lpthreads may be reuiured on some platforms instead of -pthreads
|
||||
# -ldl or -lld may be required for some platforms
|
||||
DCFLAGS ?= -fPIC
|
||||
MAILPROXY ?= false
|
||||
ifeq ($(MAILPROXY),true)
|
||||
CFLAGS += -DWITH_POP3P -DWITH_IMAPP -DWITH_SMTPP
|
||||
MAIL_EXES = $(BUILDDIR)$(PREFIX)pop3p$(EXESUFFICS) $(BUILDDIR)$(PREFIX)imapp$(EXESUFFICS) $(BUILDDIR)$(PREFIX)smtpp$(EXESUFFICS)
|
||||
endif
|
||||
FTP ?= false
|
||||
ifeq ($(FTP),true)
|
||||
CFLAGS += -DWITH_FTP
|
||||
FTP_EXES = $(BUILDDIR)$(PREFIX)ftppr$(EXESUFFICS)
|
||||
endif
|
||||
HTTPSRV ?= true
|
||||
ifeq ($(HTTPSRV),true)
|
||||
CFLAGS += -DWITH_HTTPSRV
|
||||
|
||||
@ -27,6 +27,16 @@ CFLAGS += $(EXTRA_CFLAGS)
|
||||
LDFLAGS += $(EXTRA_LDFLAGS)
|
||||
# The HTTP server serves the endpoints declared by http lines. The admin
|
||||
# interface is built on top of it, so turning it off removes both.
|
||||
MAILPROXY ?= false
|
||||
ifeq ($(MAILPROXY),true)
|
||||
CFLAGS += -DWITH_POP3P -DWITH_IMAPP -DWITH_SMTPP
|
||||
MAIL_EXES = $(BUILDDIR)$(PREFIX)pop3p$(EXESUFFICS) $(BUILDDIR)$(PREFIX)imapp$(EXESUFFICS) $(BUILDDIR)$(PREFIX)smtpp$(EXESUFFICS)
|
||||
endif
|
||||
FTP ?= false
|
||||
ifeq ($(FTP),true)
|
||||
CFLAGS += -DWITH_FTP
|
||||
FTP_EXES = $(BUILDDIR)$(PREFIX)ftppr$(EXESUFFICS)
|
||||
endif
|
||||
HTTPSRV ?= true
|
||||
ifeq ($(HTTPSRV),true)
|
||||
CFLAGS += -DWITH_HTTPSRV
|
||||
|
||||
@ -14,6 +14,16 @@ COUT = -o ./
|
||||
LN = $(CC)
|
||||
LDFLAGS = -xO3
|
||||
DCFLAGS = -fPIC
|
||||
MAILPROXY ?= false
|
||||
ifeq ($(MAILPROXY),true)
|
||||
CFLAGS += -DWITH_POP3P -DWITH_IMAPP -DWITH_SMTPP
|
||||
MAIL_EXES = $(BUILDDIR)$(PREFIX)pop3p$(EXESUFFICS) $(BUILDDIR)$(PREFIX)imapp$(EXESUFFICS) $(BUILDDIR)$(PREFIX)smtpp$(EXESUFFICS)
|
||||
endif
|
||||
FTP ?= false
|
||||
ifeq ($(FTP),true)
|
||||
CFLAGS += -DWITH_FTP
|
||||
FTP_EXES = $(BUILDDIR)$(PREFIX)ftppr$(EXESUFFICS)
|
||||
endif
|
||||
HTTPSRV ?= true
|
||||
ifeq ($(HTTPSRV),true)
|
||||
CFLAGS += -DWITH_HTTPSRV
|
||||
|
||||
@ -26,6 +26,16 @@ LDFLAGS += $(EXTRA_LDFLAGS)
|
||||
# -lpthreads may be reuqired on some platforms instead of -pthreads
|
||||
# -ldl or -lld may be required for some platforms
|
||||
DCFLAGS ?= -fPIC
|
||||
MAILPROXY ?= false
|
||||
ifeq ($(MAILPROXY),true)
|
||||
CFLAGS += -DWITH_POP3P -DWITH_IMAPP -DWITH_SMTPP
|
||||
MAIL_EXES = $(BUILDDIR)$(PREFIX)pop3p$(EXESUFFICS) $(BUILDDIR)$(PREFIX)imapp$(EXESUFFICS) $(BUILDDIR)$(PREFIX)smtpp$(EXESUFFICS)
|
||||
endif
|
||||
FTP ?= false
|
||||
ifeq ($(FTP),true)
|
||||
CFLAGS += -DWITH_FTP
|
||||
FTP_EXES = $(BUILDDIR)$(PREFIX)ftppr$(EXESUFFICS)
|
||||
endif
|
||||
HTTPSRV ?= true
|
||||
ifeq ($(HTTPSRV),true)
|
||||
CFLAGS += -DWITH_HTTPSRV
|
||||
|
||||
10
Makefile.win
10
Makefile.win
@ -23,6 +23,16 @@ LDFLAGS += -fno-strict-aliasing -mthreads
|
||||
# makefile, including the += above and the STATIC/LIBSTATIC handling below.
|
||||
CFLAGS += $(EXTRA_CFLAGS)
|
||||
LDFLAGS += $(EXTRA_LDFLAGS)
|
||||
MAILPROXY ?= false
|
||||
ifeq ($(MAILPROXY),true)
|
||||
CFLAGS += -DWITH_POP3P -DWITH_IMAPP -DWITH_SMTPP
|
||||
MAIL_EXES = $(BUILDDIR)$(PREFIX)pop3p$(EXESUFFICS) $(BUILDDIR)$(PREFIX)imapp$(EXESUFFICS) $(BUILDDIR)$(PREFIX)smtpp$(EXESUFFICS)
|
||||
endif
|
||||
FTP ?= false
|
||||
ifeq ($(FTP),true)
|
||||
CFLAGS += -DWITH_FTP
|
||||
FTP_EXES = $(BUILDDIR)$(PREFIX)ftppr$(EXESUFFICS)
|
||||
endif
|
||||
HTTPSRV ?= true
|
||||
ifeq ($(HTTPSRV),true)
|
||||
CFLAGS += -DWITH_HTTPSRV
|
||||
|
||||
18
SECURITY.md
18
SECURITY.md
@ -34,8 +34,8 @@ For High/Critical patched version is released within 2 weeks
|
||||
|
||||
## Verifying downloads
|
||||
|
||||
Release binaries are published with SHA256 checksums, an OpenPGP signature and
|
||||
a GitHub build provenance attestation.
|
||||
Release binaries and the source tarball are published with SHA256 checksums, an
|
||||
OpenPGP signature and a GitHub build provenance attestation.
|
||||
|
||||
The release signing key is `3proxy-release-key.asc` in the root of this
|
||||
repository, an RSA-4096 key:
|
||||
@ -59,6 +59,18 @@ gpg --verify SHA256SUMS-x86_64.asc SHA256SUMS-x86_64
|
||||
sha256sum -c SHA256SUMS-x86_64
|
||||
```
|
||||
|
||||
The source tarball published with each release is signed as well:
|
||||
|
||||
```
|
||||
gpg --verify SHA256SUMS-src.asc SHA256SUMS-src
|
||||
sha256sum -c SHA256SUMS-src
|
||||
gpg --verify 3proxy-0.9.9.tar.gz.asc 3proxy-0.9.9.tar.gz
|
||||
```
|
||||
|
||||
Prefer it over the `Source code (tar.gz)` link GitHub generates automatically:
|
||||
only the published tarball is signed. It is produced with `git archive` from
|
||||
the release tag, so it can be regenerated and compared byte for byte.
|
||||
|
||||
RPM packages are signed, the signature is checked by rpm itself:
|
||||
|
||||
```
|
||||
@ -79,5 +91,3 @@ verified with the GitHub CLI:
|
||||
gh attestation verify 3proxy-0.9.9.x86_64.rpm --owner 3proxy
|
||||
gh attestation verify oci://docker.io/3proxy/3proxy:latest --owner 3proxy
|
||||
```
|
||||
|
||||
Windows binaries are Authenticode signed in addition to the above.
|
||||
|
||||
@ -1942,10 +1942,12 @@ allowed traffic in megabytes (MB). nocountin allows you to set exclusions.
|
||||
For name resolution and caching, use the commands nserver, nscache / nscache6, and nsrecord.
|
||||
<pre>
|
||||
nserver 192.168.1.2
|
||||
nserver 192.168.1.3:5353/tcp</pre>
|
||||
nserver 192.168.1.3:5353/tcp
|
||||
nserver [2001:4860:4860::8844]</pre>
|
||||
sets DNS resolvers. 192.168.1.3 will be used via TCP/5353 (instead of default UDP/53)
|
||||
only if 192.168.1.2 fails. Up to 5 nservers may be specified.
|
||||
If no nserver is configured, default system name resolution functions are used.
|
||||
An IPv6 address has to be written in square brackets.
|
||||
<pre>
|
||||
nscache 65535
|
||||
nscache6 65535</pre>
|
||||
|
||||
@ -1993,11 +1993,13 @@ socks -p1080
|
||||
nscache и nsrecord.
|
||||
<pre>
|
||||
nserver 192.168.1.2
|
||||
nserver 192.168.1.3:5353/tcp</pre>
|
||||
nserver 192.168.1.3:5353/tcp
|
||||
nserver [2001:4860:4860::8844]</pre>
|
||||
указывает 3proxy какие машины следует использвоать в качестве серверов
|
||||
DNS. Сервер 192.168.1.3 будет использоваться по порту TCP/5353 (вместо дефолтного UDP/53) только при недостижимости
|
||||
192.168.1.2. Можно указать до 5 серверов. Если nserver не указан, будут
|
||||
использованы системные функции разрешения имен.
|
||||
Адрес IPv6 необходимо записывать в квадратных скобках.
|
||||
<pre>
|
||||
nscache 65535
|
||||
nscache6 65535</pre>
|
||||
|
||||
@ -18,6 +18,7 @@
|
||||
<a href="#PCRE FILTERING">PCRE FILTERING</a><br>
|
||||
<a href="#PCRE Commands">PCRE Commands</a><br>
|
||||
<a href="#PCRE Parameters">PCRE Parameters</a><br>
|
||||
<a href="#OPTIONAL SERVICES">OPTIONAL SERVICES</a><br>
|
||||
<a href="#BUILT IN HTTP SERVER">BUILT IN HTTP SERVER</a><br>
|
||||
<a href="#Operations">Operations</a><br>
|
||||
<a href="#What a rule adds to the answer">What a rule adds to the answer</a><br>
|
||||
@ -114,10 +115,17 @@ handshake), may be used to redirect any TLS-based traffic
|
||||
<b><br>
|
||||
auto</b> Proxy with protocol autoselection between proxy /
|
||||
socks / tlspr <b><br>
|
||||
pop3p</b> POP3 proxy (default port 110) <b><br>
|
||||
imapp</b> IMAPv4 proxy (default port 143) <b><br>
|
||||
smtpp</b> SMTP proxy (default port 25) <b><br>
|
||||
ftppr</b> FTP proxy (default port 21) <b><br>
|
||||
pop3p</b> POP3 proxy (default port 110), in a build which
|
||||
has one: otherwise the service is <b>tlspr</b> speaking POP3
|
||||
to negotiate STARTTLS, under this name. <b><br>
|
||||
imapp</b> IMAPv4 proxy (default port 143), or <b>tlspr</b>
|
||||
speaking IMAP, as above. <b><br>
|
||||
smtpp</b> SMTP proxy (default port 25), or <b>tlspr</b>
|
||||
speaking SMTP, as above. <b><br>
|
||||
ftppr</b> FTP proxy (default port 21), in a build with FTP
|
||||
support. Without it the service is known but answers
|
||||
nothing, and <b>ftp://</b> is not a URL the HTTP proxy
|
||||
fetches. <b><br>
|
||||
admin</b> Web interface (default port 80) <b><br>
|
||||
dnspr</b> caching DNS proxy (default port 53) <b><br>
|
||||
tcppm</b> TCP portmapper. Destination address (DSTADDR) can
|
||||
@ -492,7 +500,11 @@ experimental.</p>
|
||||
Nameserver to use for name resolutions. If none specified
|
||||
system routines for name resolution is used. Optional port
|
||||
number may be specified. If optional /tcp is added to IP
|
||||
address, name resolution is performed over TCP.</p>
|
||||
address, name resolution is performed over TCP. An IPv6
|
||||
address has to be enclosed in square brackets: <b><br>
|
||||
nserver 1.1.1.1 <br>
|
||||
nserver [2001:4860:4860::8844] <br>
|
||||
nserver [2001:4860:4860::8844]:5353/tcp</b></p>
|
||||
|
||||
|
||||
<p style="margin-left:9%; margin-top: 1em"><b>authnserver</b>
|
||||
@ -824,15 +836,40 @@ grouped. Proxy inside the group is selected randomly. If few
|
||||
groups are specified one proxy is randomly picked from each
|
||||
group and chain of proxies is created (that is second proxy
|
||||
connected through first one and so on). Weight is used to
|
||||
group proxies. Weight is a number between 1 and 1000.
|
||||
group proxies. A weight is a share of the whole, written
|
||||
either as a fraction of one, anything beginning with 0 or
|
||||
with a point, or the old way, in thousandths: <b><br>
|
||||
.5</b> and <b>0.5</b> and <b>500</b> are all a half <b><br>
|
||||
.333</b> and <b>333</b> are both 333 thousandths <b><br>
|
||||
1000</b> and <b>1.0</b> and <b>100%</b> are all the whole
|
||||
share <b><br>
|
||||
50.5%</b> and <b>.505</b> and <b>505</b> are all the same
|
||||
share <b><br>
|
||||
0</b> is a fallback, described below <br>
|
||||
A fraction takes up to nine digits after the point,
|
||||
<b>.333333333</b> being the finest share there is, and the
|
||||
old notation may now carry further digits after a point of
|
||||
its own, so <b>123.456</b> means the same as <b>.123456</b>.
|
||||
Weights are scanned as integers, nothing is read as a
|
||||
floating point number. <b>1.0</b>, with as many zeroes after
|
||||
the point as you care to write, is the one weight read as it
|
||||
looks rather than in thousandths, so that the whole share
|
||||
can be written as a fraction too: a bare <b>1</b> is still a
|
||||
thousandth of it, and <b>1.5</b> still one and a half of
|
||||
them. A weight ending in <b>%</b> is a percentage, and takes
|
||||
up to seven digits after the point. <br>
|
||||
Weights are summed and proxies are grouped together until
|
||||
the weight of the group is 1000. That is: <br>
|
||||
the weight of the group is the whole share. A group which
|
||||
falls short of it by no more than a thousandth, which three
|
||||
weights of <b>333</b> do, is taken for a whole one rather
|
||||
than for a group with a remainder, so a share which cannot
|
||||
be divided evenly needs no adjusting by hand. That is: <br>
|
||||
allow * <br>
|
||||
parent 500 socks5 192.168.10.1 1080 <br>
|
||||
parent 500 connect 192.168.10.1 3128 <br>
|
||||
makes 3proxy to randomly choose between 2 proxies for all
|
||||
outgoing connections. These 2 proxies form 1 group
|
||||
(summarized weight is 1000). <br>
|
||||
outgoing connections. These 2 proxies form 1 group (their
|
||||
weights are the whole share between them). <br>
|
||||
allow * * * 80 <br>
|
||||
parent 1000 socks5 192.168.10.1 1080 <br>
|
||||
parent 1000 connect 192.168.20.1 3128 <br>
|
||||
@ -972,13 +1009,36 @@ Changes the external address for a given connection to
|
||||
1.2.3.4 (equivalent to <b>-e1.2.3.4</b>) <br>
|
||||
Optional username and password are used to authenticate on
|
||||
parent proxy. Username of ´*´ means username
|
||||
must be supplied by user.</p>
|
||||
must be supplied by user. <br>
|
||||
A parent which fails is taken out of the choice for the rest
|
||||
of that connection, so the next attempt, see
|
||||
<b>parentretries</b>, goes to another member of the group
|
||||
instead of the same parent again. Its share is spread over
|
||||
the parents of the group which are left, in proportion to
|
||||
their weights. <br>
|
||||
Weight 0 marks a fallback parent. Such a parent takes no
|
||||
share of the random choice, and none of the share left over
|
||||
by a parent which failed, and is only used once every
|
||||
weighted parent of its group has failed, which is how a
|
||||
parent used only when another one is down is configured:
|
||||
<br>
|
||||
allow * <br>
|
||||
parent 1000 socks5 192.168.10.1 1080 <br>
|
||||
parent 0 socks5 192.168.20.1 1080 <br>
|
||||
Several fallbacks are tried in the order they are written.
|
||||
Reaching a fallback costs an attempt, so
|
||||
<b>parentretries</b> has to be at least as large as the
|
||||
number of parents to try. <br>
|
||||
When every parent of a group has failed the request fails as
|
||||
well, rather than being sent without a parent.</p>
|
||||
|
||||
|
||||
<p style="margin-left:9%; margin-top: 1em"><b>parentretries</b>
|
||||
<i><number></i> <br>
|
||||
Number of retries to connect to parent proxy. Default is
|
||||
1.</p>
|
||||
Number of attempts to reach a parent proxy. Default is 2.
|
||||
Each attempt picks a parent again, leaving out the ones
|
||||
which already failed, so this is also the number of
|
||||
different parents a request may be tried through.</p>
|
||||
|
||||
<p style="margin-left:9%; margin-top: 1em"><b>nolog</b>
|
||||
<i><n></i> <br>
|
||||
@ -1516,6 +1576,29 @@ the connection data. Warning: Regular expressions
|
||||
don’t require authentication and cannot replace
|
||||
authentication and/or allow/deny ACLs.</p>
|
||||
|
||||
<h2>OPTIONAL SERVICES
|
||||
<a name="OPTIONAL SERVICES"></a>
|
||||
</h2>
|
||||
|
||||
|
||||
<p style="margin-left:9%; margin-top: 1em">The mail proxies
|
||||
and FTP are built when they are asked for, and are not in a
|
||||
default build. <b>MAILPROXY=true</b> builds <b>pop3p</b>,
|
||||
<b>imapp</b> and <b>smtpp</b>, and <b>FTP=true</b> builds
|
||||
<b>ftppr</b> and the <b>ftp://</b> scheme of the HTTP proxy;
|
||||
with CMake the switches are <b>-D3PROXY_USE_MAILPROXY=ON</b>
|
||||
and <b>-D3PROXY_USE_FTP=ON</b>. The standalone binaries of
|
||||
those services are built with them and not without. <br>
|
||||
A configuration naming a service which was not built is
|
||||
still read. The three mail proxies become <b>tlspr</b>
|
||||
negotiating STARTTLS in that protocol, which is what most of
|
||||
their use amounts to now that the mail protocols are used
|
||||
over TLS, and a <b>parent</b> chain naming <b>pop3</b>,
|
||||
<b>imap</b> or <b>smtp</b> does the same. <b>ftppr</b> is
|
||||
answered by nothing: a client reaching it is turned away and
|
||||
the refusal logged, since there is no protocol to fall back
|
||||
on.</p>
|
||||
|
||||
<h2>BUILT IN HTTP SERVER
|
||||
<a name="BUILT IN HTTP SERVER"></a>
|
||||
</h2>
|
||||
|
||||
@ -95,16 +95,19 @@ SNI proxy (destination address is taken from TLS handshake), may be used to redi
|
||||
Proxy with protocol autoselection between proxy / socks / tlspr
|
||||
.br
|
||||
.B pop3p
|
||||
POP3 proxy (default port 110)
|
||||
POP3 proxy (default port 110), in a build which has one: otherwise the
|
||||
service is \fBtlspr\fR speaking POP3 to negotiate STARTTLS, under this name.
|
||||
.br
|
||||
.B imapp
|
||||
IMAPv4 proxy (default port 143)
|
||||
IMAPv4 proxy (default port 143), or \fBtlspr\fR speaking IMAP, as above.
|
||||
.br
|
||||
.B smtpp
|
||||
SMTP proxy (default port 25)
|
||||
SMTP proxy (default port 25), or \fBtlspr\fR speaking SMTP, as above.
|
||||
.br
|
||||
.B ftppr
|
||||
FTP proxy (default port 21)
|
||||
FTP proxy (default port 21), in a build with FTP support. Without it the
|
||||
service is known but answers nothing, and \fBftp://\fR is not a URL the HTTP
|
||||
proxy fetches.
|
||||
.br
|
||||
.B admin
|
||||
Web interface (default port 80)
|
||||
@ -508,6 +511,13 @@ system routines for name resolution is
|
||||
used. Optional port number may be specified.
|
||||
If optional /tcp is added to IP address, name resolution is
|
||||
performed over TCP.
|
||||
An IPv6 address has to be enclosed in square brackets:
|
||||
.br
|
||||
\fBnserver 1.1.1.1\fR
|
||||
.br
|
||||
\fBnserver [2001:4860:4860::8844]\fR
|
||||
.br
|
||||
\fBnserver [2001:4860:4860::8844]:5353/tcp\fR
|
||||
|
||||
.br
|
||||
.BR authnserver
|
||||
@ -860,9 +870,35 @@ build proxy chain. Proxies may be grouped. Proxy inside the
|
||||
group is selected randomly. If few groups are specified one proxy
|
||||
is randomly picked from each group and chain of proxies is created
|
||||
(that is second proxy connected through first one and so on).
|
||||
Weight is used to group proxies. Weight is a number between 1 and 1000.
|
||||
Weights are summed and proxies are grouped together until the weight of
|
||||
the group is 1000. That is:
|
||||
Weight is used to group proxies. A weight is a share of the whole, written
|
||||
either as a fraction of one, anything beginning with 0 or with a point, or the
|
||||
old way, in thousandths:
|
||||
.br
|
||||
\fB.5\fR and \fB0.5\fR and \fB500\fR are all a half
|
||||
.br
|
||||
\fB.333\fR and \fB333\fR are both 333 thousandths
|
||||
.br
|
||||
\fB1000\fR and \fB1.0\fR and \fB100%\fR are all the whole share
|
||||
.br
|
||||
\fB50.5%\fR and \fB.505\fR and \fB505\fR are all the same share
|
||||
.br
|
||||
\fB0\fR is a fallback, described below
|
||||
.br
|
||||
A fraction takes up to nine digits after the point, \fB.333333333\fR being
|
||||
the finest share there is, and the old notation may now carry further digits
|
||||
after a point of its own, so \fB123.456\fR means the same as \fB.123456\fR.
|
||||
Weights are scanned as integers, nothing is read as a floating point number.
|
||||
\fB1.0\fR, with as many zeroes after the point as you care to write, is the
|
||||
one weight read as it looks rather than in thousandths, so that the whole
|
||||
share can be written as a fraction too: a bare \fB1\fR is still a thousandth
|
||||
of it, and \fB1.5\fR still one and a half of them. A weight ending in
|
||||
\fB%\fR is a percentage, and takes up to seven digits after the point.
|
||||
.br
|
||||
Weights are summed and proxies are grouped together until the weight of
|
||||
the group is the whole share. A group which falls short of it by no more than
|
||||
a thousandth, which three weights of \fB333\fR do, is taken for a whole one
|
||||
rather than for a group with a remainder, so a share which cannot be divided
|
||||
evenly needs no adjusting by hand. That is:
|
||||
.br
|
||||
allow *
|
||||
.br
|
||||
@ -871,7 +907,8 @@ the group is 1000. That is:
|
||||
parent 500 connect 192.168.10.1 3128
|
||||
.br
|
||||
makes 3proxy to randomly choose between 2 proxies for all outgoing
|
||||
connections. These 2 proxies form 1 group (summarized weight is 1000).
|
||||
connections. These 2 proxies form 1 group (their weights are the whole share
|
||||
between them).
|
||||
.br
|
||||
allow * * * 80
|
||||
.br
|
||||
@ -1004,12 +1041,37 @@ local HTTP proxy parses requests and allows only GET and POST requests.
|
||||
.br
|
||||
Optional username and password are used to authenticate on parent
|
||||
proxy. Username of \'*\' means username must be supplied by user.
|
||||
.br
|
||||
A parent which fails is taken out of the choice for the rest of that
|
||||
connection, so the next attempt, see \fBparentretries\fR, goes to another
|
||||
member of the group instead of the same parent again. Its share is spread over
|
||||
the parents of the group which are left, in proportion to their weights.
|
||||
.br
|
||||
Weight 0 marks a fallback parent. Such a parent takes no share of the random
|
||||
choice, and none of the share left over by a parent which failed, and is only
|
||||
used once every weighted parent of its group has failed,
|
||||
which is how a parent used only when another one is down is configured:
|
||||
.br
|
||||
allow *
|
||||
.br
|
||||
parent 1000 socks5 192.168.10.1 1080
|
||||
.br
|
||||
parent 0 socks5 192.168.20.1 1080
|
||||
.br
|
||||
Several fallbacks are tried in the order they are written. Reaching a fallback
|
||||
costs an attempt, so \fBparentretries\fR has to be at least as large as the
|
||||
number of parents to try.
|
||||
.br
|
||||
When every parent of a group has failed the request fails as well, rather
|
||||
than being sent without a parent.
|
||||
|
||||
.br
|
||||
.BR parentretries
|
||||
\fI<number>\fR
|
||||
.br
|
||||
Number of retries to connect to parent proxy. Default is 1.
|
||||
Number of attempts to reach a parent proxy. Default is 2. Each attempt
|
||||
picks a parent again, leaving out the ones which already failed, so this is
|
||||
also the number of different parents a request may be tried through.
|
||||
|
||||
|
||||
.br
|
||||
@ -1573,6 +1635,21 @@ matched if the ACL matches the connection data.
|
||||
Warning: Regular expressions don't require authentication and cannot replace
|
||||
authentication and/or allow/deny ACLs.
|
||||
|
||||
.SH OPTIONAL SERVICES
|
||||
The mail proxies and FTP are built when they are asked for, and are not in a
|
||||
default build. \fBMAILPROXY=true\fR builds \fBpop3p\fR, \fBimapp\fR and
|
||||
\fBsmtpp\fR, and \fBFTP=true\fR builds \fBftppr\fR and the \fBftp://\fR
|
||||
scheme of the HTTP proxy; with CMake the switches are
|
||||
\fB-D3PROXY_USE_MAILPROXY=ON\fR and \fB-D3PROXY_USE_FTP=ON\fR. The standalone
|
||||
binaries of those services are built with them and not without.
|
||||
.br
|
||||
A configuration naming a service which was not built is still read. The three
|
||||
mail proxies become \fBtlspr\fR negotiating STARTTLS in that protocol, which
|
||||
is what most of their use amounts to now that the mail protocols are used over
|
||||
TLS, and a \fBparent\fR chain naming \fBpop3\fR, \fBimap\fR or \fBsmtp\fR
|
||||
does the same. \fBftppr\fR is answered by nothing: a client reaching it is
|
||||
turned away and the refusal logged, since there is no protocol to fall back on.
|
||||
|
||||
.SH BUILT IN HTTP SERVER
|
||||
The \fBhttpsrv\fR service answers requests itself instead of forwarding them.
|
||||
What it does with a request is decided by \fBhttp\fR rules, which are taken in
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
# 3 proxy common Makefile
|
||||
#
|
||||
|
||||
all: $(BUILDDIR)3proxy$(EXESUFFICS) $(BUILDDIR)$(CRYPT_PREFIX)crypt$(EXESUFFICS) $(BUILDDIR)$(PREFIX)pop3p$(EXESUFFICS) $(BUILDDIR)$(PREFIX)imapp$(EXESUFFICS) $(BUILDDIR)$(PREFIX)smtpp$(EXESUFFICS) $(BUILDDIR)$(PREFIX)ftppr$(EXESUFFICS) $(BUILDDIR)$(PREFIX)tcppm$(EXESUFFICS) $(BUILDDIR)$(PREFIX)udppm$(EXESUFFICS) $(BUILDDIR)$(PREFIX)tlspr$(EXESUFFICS) $(BUILDDIR)$(PREFIX)socks$(EXESUFFICS) $(BUILDDIR)$(PREFIX)proxy$(EXESUFFICS) allplugins
|
||||
all: $(BUILDDIR)3proxy$(EXESUFFICS) $(BUILDDIR)$(CRYPT_PREFIX)crypt$(EXESUFFICS) $(MAIL_EXES) $(FTP_EXES) $(BUILDDIR)$(PREFIX)tcppm$(EXESUFFICS) $(BUILDDIR)$(PREFIX)udppm$(EXESUFFICS) $(BUILDDIR)$(PREFIX)tlspr$(EXESUFFICS) $(BUILDDIR)$(PREFIX)socks$(EXESUFFICS) $(BUILDDIR)$(PREFIX)proxy$(EXESUFFICS) allplugins
|
||||
|
||||
sockmap$(OBJSUFFICS): sockmap.c proxy.h structures.h
|
||||
$(CC) $(CFLAGS) sockmap.c
|
||||
|
||||
85
src/conf.c
85
src/conf.c
@ -903,6 +903,82 @@ static int parserange(unsigned char *arg, uint32_t *range)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Scan a parent weight into its share of WEIGHTSCALE, as an integer: there is
|
||||
* no floating point anywhere near a configuration file.
|
||||
*
|
||||
* A weight starting with 0 or . is a fraction of one, so .333 and 0.333 are
|
||||
* both a third. Anything else is the old notation, thousandths, where 1000 is
|
||||
* the whole share, and it may now carry more digits after a dot: 123.456 means
|
||||
* the same as .123456. Either way at most 9 digits are kept, which is the
|
||||
* resolution weights are held at.
|
||||
*
|
||||
* 1 followed by a point and nothing but zeroes is the one weight read as it
|
||||
* looks rather than as thousandths: 1.0 is the whole share, where a bare 1 is
|
||||
* a thousandth of it.
|
||||
*
|
||||
* A weight may also be written as a percentage, which is what a trailing %
|
||||
* makes it: 50.5% is .505 is 505.
|
||||
*/
|
||||
static int parseweight(unsigned char * s, unsigned * weight){
|
||||
static const unsigned pow10[10] = {1, 10, 100, 1000, 10000, 100000,
|
||||
1000000, 10000000, 100000000, 1000000000};
|
||||
unsigned char *p, *end;
|
||||
uint64_t val = 0, res;
|
||||
int ndigits = 0, atpoint = -1, after, percent = 0, fraction;
|
||||
|
||||
if(!s || !*s) return 1;
|
||||
end = s + strlen((char *)s);
|
||||
if(end[-1] == '%'){
|
||||
percent = 1;
|
||||
if(--end == s) return 1;
|
||||
}
|
||||
/* 1.0, with as many zeroes after it as anyone cares to write, is the one
|
||||
weight read as it looks rather than as thousandths: the whole share,
|
||||
where a bare 1 is a thousandth of it */
|
||||
if(!percent && s[0] == '1' && s[1] == '.' && s[2]){
|
||||
for(p = s + 2; *p == '0'; p++);
|
||||
if(!*p){
|
||||
*weight = WEIGHTSCALE;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
fraction = (*s == '.' || *s == '0');
|
||||
for(p = s; p < end; p++){
|
||||
if(*p == '.'){
|
||||
if(atpoint >= 0) return 1;
|
||||
atpoint = ndigits;
|
||||
continue;
|
||||
}
|
||||
if(*p < '0' || *p > '9') return 1;
|
||||
if(ndigits == 18) return 1;
|
||||
val = (val * 10) + (unsigned)(*p - '0');
|
||||
ndigits++;
|
||||
}
|
||||
if(!ndigits || val > WEIGHTSCALE) return 1;
|
||||
after = (atpoint < 0)? 0 : ndigits - atpoint;
|
||||
if(percent){
|
||||
/* a hundredth of the whole share for every 1% */
|
||||
if(after > 7) return 1;
|
||||
res = val * pow10[7 - after];
|
||||
}
|
||||
else if(fraction){
|
||||
/* the digits before the point are the leading zero and add
|
||||
nothing, so only the ones after it say what the share is */
|
||||
if(atpoint < 0) return val? 1 : (*weight = 0, 0);
|
||||
if(after > 9) return 1;
|
||||
res = val * pow10[9 - after];
|
||||
}
|
||||
else {
|
||||
/* thousandths, with the digits after the point carrying on
|
||||
from them: three digits of a whole share, six more after */
|
||||
if(after > 6) return 1;
|
||||
res = val * pow10[6 - after];
|
||||
}
|
||||
if(res > WEIGHTSCALE) return 1;
|
||||
*weight = (unsigned)res;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int h_parent(int argc, unsigned char **argv){
|
||||
struct ace *acl = NULL;
|
||||
struct chain *chains;
|
||||
@ -922,9 +998,10 @@ static int h_parent(int argc, unsigned char **argv){
|
||||
return(21);
|
||||
}
|
||||
memset(chains, 0, sizeof(struct chain));
|
||||
chains->weight = (unsigned)atoi((char *)argv[1]);
|
||||
if(chains->weight == 0 || chains->weight >1000) {
|
||||
fprintf(stderr, "Chaining error: bad chain weight %u line %d\n", chains->weight, linenum);
|
||||
/* 0 is the fallback weight: such a parent is only used once every
|
||||
weighted parent of its group has failed */
|
||||
if(parseweight(argv[1], &chains->weight)) {
|
||||
fprintf(stderr, "Chaining error: bad chain weight %s line %d\n", argv[1], linenum);
|
||||
free(chains);
|
||||
return(3);
|
||||
}
|
||||
@ -1497,7 +1574,7 @@ static int h_ace(int argc, unsigned char **argv){
|
||||
return 5;
|
||||
}
|
||||
*SAPORT(&acl->chains->addr) = htons((uint16_t)atoi((char *)argv[2]));
|
||||
acl->chains->weight = 1000;
|
||||
acl->chains->weight = WEIGHTSCALE;
|
||||
case ALLOW:
|
||||
case DENY:
|
||||
if(!conf.acl){
|
||||
|
||||
@ -802,7 +802,7 @@ static struct property prop_pwlist[] = {
|
||||
static struct property prop_chain[] = {
|
||||
{"addr", ef_chain_addr, TYPE_SA, "parent address"},
|
||||
{"type", ef_chain_type, TYPE_STRING, "parent type"},
|
||||
{"weight", ef_chain_weight, TYPE_SHORT, "parent weight 0-1000"},
|
||||
{"weight", ef_chain_weight, TYPE_INTEGER, "parent weight, 1000000000 is the whole share, 0 is a fallback"},
|
||||
{"user", ef_chain_user, TYPE_STRING, "parent login"},
|
||||
{"password", ef_chain_password, TYPE_PASSWORD, "parent password"},
|
||||
{"secure", ef_chain_secure, TYPE_INTEGER, "secure mode"},
|
||||
|
||||
@ -7,6 +7,8 @@
|
||||
|
||||
#include "proxy.h"
|
||||
|
||||
#ifdef WITH_FTP
|
||||
|
||||
/*
|
||||
* Read one FTP server response, skipping continuation lines (lines whose
|
||||
* 4th character is '-' per RFC 959). Returns the line length on success,
|
||||
@ -249,3 +251,5 @@ SOCKET ftpcommand(struct clientparam *param, unsigned char * command, unsigned c
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
15
src/ftppr.c
15
src/ftppr.c
@ -8,6 +8,8 @@
|
||||
|
||||
#include "proxy.h"
|
||||
|
||||
#ifdef WITH_FTP
|
||||
|
||||
#define RETURN(xxx) { param->res = xxx; goto CLEANRET; }
|
||||
#define BUFSIZE 2048
|
||||
|
||||
@ -345,3 +347,16 @@ struct proxydef childdef = {
|
||||
};
|
||||
#include "proxymain.c"
|
||||
#endif
|
||||
|
||||
#else
|
||||
|
||||
/* Built without FTP support. The service and the redirect naming it are
|
||||
still known, so a configuration carrying them is read rather than
|
||||
refused, and a client reaching one is turned away. */
|
||||
void * ftpprchild(struct clientparam * param){
|
||||
param->res = 878;
|
||||
dolog(param, (unsigned char *)"ftp support is not built in");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
14
src/imapp.c
14
src/imapp.c
@ -8,6 +8,8 @@
|
||||
|
||||
#include "proxy.h"
|
||||
|
||||
#ifdef WITH_IMAPP
|
||||
|
||||
#define RETURN(xxx) { param->res = xxx; goto CLEANRET; }
|
||||
|
||||
#define CL_LOGINCMD 0
|
||||
@ -250,3 +252,15 @@ struct proxydef childdef = {
|
||||
};
|
||||
#include "proxymain.c"
|
||||
#endif
|
||||
#else
|
||||
|
||||
/* Built without this proxy of its own. The command and the redirect
|
||||
naming it are the STARTTLS proxy speaking that protocol, which
|
||||
negotiates the same way and passes the session on: what "tlspr -Ximap"
|
||||
does, under the name a configuration already uses. */
|
||||
void * imappchild(struct clientparam * param){
|
||||
param->starttls = S_IMAPP;
|
||||
return (void *)tlsprchild;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
14
src/pop3p.c
14
src/pop3p.c
@ -8,6 +8,8 @@
|
||||
|
||||
#include "proxy.h"
|
||||
|
||||
#ifdef WITH_POP3P
|
||||
|
||||
#define RETURN(xxx) { param->res = xxx; goto CLEANRET; }
|
||||
|
||||
#ifdef WITHMAIN
|
||||
@ -88,3 +90,15 @@ struct proxydef childdef = {
|
||||
};
|
||||
#include "proxymain.c"
|
||||
#endif
|
||||
#else
|
||||
|
||||
/* Built without this proxy of its own. The command and the redirect
|
||||
naming it are the STARTTLS proxy speaking that protocol, which
|
||||
negotiates the same way and passes the session on: what "tlspr -Xpop3"
|
||||
does, under the name a configuration already uses. */
|
||||
void * pop3pchild(struct clientparam * param){
|
||||
param->starttls = S_POP3P;
|
||||
return (void *)tlsprchild;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@ -385,10 +385,12 @@ for(;;){
|
||||
if (!strncasecmp((char *)sb, "http://", 7)) {
|
||||
sb += 7;
|
||||
}
|
||||
#ifdef WITH_FTP
|
||||
else if (!strncasecmp((char *)sb, "ftp://", 6)) {
|
||||
ftp = 1;
|
||||
sb += 6;
|
||||
}
|
||||
#endif
|
||||
else if(*sb == '/') {
|
||||
param->transparent = 1;
|
||||
}
|
||||
@ -712,6 +714,7 @@ for(;;){
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef WITH_FTP
|
||||
if(ftp && param->redirtype != R_HTTP){
|
||||
SOCKET s;
|
||||
int mode = 0;
|
||||
@ -963,6 +966,7 @@ for(;;){
|
||||
}
|
||||
RETURN(res);
|
||||
}
|
||||
#endif
|
||||
|
||||
if(isconnect && param->redirtype != R_HTTP) {
|
||||
if(param->redirectfunc) {
|
||||
|
||||
136
src/redirect.c
136
src/redirect.c
@ -273,45 +273,121 @@ void applyportranges(struct clientparam * param, struct ace * acentry){
|
||||
}
|
||||
}
|
||||
|
||||
static void chainaddr(struct chain * cur, PROXYSOCKADDRTYPE * sa){
|
||||
PROXYSOCKADDRTYPE fresh;
|
||||
|
||||
*sa = cur->addr;
|
||||
if(resolvfunc != myresolver) return;
|
||||
if(!cur->exthost || SAISNULL(&cur->addr)) return;
|
||||
#ifdef WITH_UN
|
||||
if(*SAFAMILY(&cur->addr) == AF_UNIX) return;
|
||||
#endif
|
||||
if(afdetect(cur->exthost) != -1) return;
|
||||
memset(&fresh, 0, sizeof(fresh));
|
||||
if(!getip46(46, cur->exthost, (struct sockaddr *)&fresh)) return;
|
||||
*SAPORT(&fresh) = *SAPORT(&cur->addr);
|
||||
*sa = fresh;
|
||||
}
|
||||
|
||||
static int parentfailed(struct clientparam * param, struct chain * ch){
|
||||
int i;
|
||||
|
||||
for(i = 0; i < param->nfailedparents; i++)
|
||||
if(param->failedparents[i] == ch) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Remember a parent which could not be used for this connection, so that a
|
||||
* retry picks another member of its group. The list is per connection: a
|
||||
* parent which is down for one client is not taken away from the others.
|
||||
*/
|
||||
static void parentfail(struct clientparam * param, struct chain * ch){
|
||||
if(!ch || param->nfailedparents >= MAXFAILEDPARENTS) return;
|
||||
if(parentfailed(param, ch)) return;
|
||||
param->failedparents[param->nfailedparents++] = ch;
|
||||
}
|
||||
|
||||
/* Pick the parent to use for one group.
|
||||
*
|
||||
* A group is the members whose weights add up to WEIGHTSCALE, together with
|
||||
* the zero weight members among them. *after is left pointing at the group
|
||||
* after this one, or at NULL. A group which lands within WEIGHTFUZZ of the
|
||||
* whole share counts as a whole one, so that 333 three times over is a group
|
||||
* rather than a group and a remainder of a thousandth.
|
||||
*
|
||||
* A member which already failed for this connection is not offered again and
|
||||
* its weight is given to the others, so a retry goes somewhere else. Zero
|
||||
* weight members are the fallback: they are only offered once every weighted
|
||||
* member of the group has failed, and they never take part in the share.
|
||||
* Where the weights add up to plainly less than the whole share the remainder
|
||||
* keeps its meaning of "no parent at all" and is still counted, so such a
|
||||
* group can still leave the connection direct.
|
||||
*
|
||||
* Returns NULL when the group adds no parent. *exhausted tells the two cases
|
||||
* apart: it is set when the group had parents and all of them are gone, which
|
||||
* is a failure rather than a reason to connect directly.
|
||||
*/
|
||||
static struct chain * pickchain(struct clientparam * param, struct chain * group,
|
||||
struct chain ** after, int * exhausted){
|
||||
struct chain *cur;
|
||||
uint64_t total = 0, avail = 0, slack;
|
||||
uint64_t r;
|
||||
|
||||
*exhausted = 0;
|
||||
for(cur = group; cur; cur = cur->next){
|
||||
if(total + WEIGHTFUZZ >= WEIGHTSCALE && cur->weight) break;
|
||||
total += cur->weight;
|
||||
if(cur->weight && !parentfailed(param, cur)) avail += cur->weight;
|
||||
}
|
||||
*after = cur;
|
||||
|
||||
if(avail){
|
||||
slack = (total + WEIGHTFUZZ < WEIGHTSCALE)? WEIGHTSCALE - total : 0;
|
||||
r = ((uint64_t)myrand() << 32 | myrand()) % (avail + slack);
|
||||
for(cur = group; cur != *after; cur = cur->next){
|
||||
if(!cur->weight || parentfailed(param, cur)) continue;
|
||||
if(r < cur->weight) return cur;
|
||||
r -= cur->weight;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
for(cur = group; cur != *after; cur = cur->next){
|
||||
if(!cur->weight && !parentfailed(param, cur)) return cur;
|
||||
}
|
||||
if(total) *exhausted = 1;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int handleredirect(struct clientparam * param, struct ace * acentry){
|
||||
int connected = 0;
|
||||
int weight = 1000;
|
||||
int res;
|
||||
int done = 0;
|
||||
int ha = 0;
|
||||
struct chain * cur;
|
||||
struct chain * after;
|
||||
struct chain * redir = NULL;
|
||||
int r2;
|
||||
int saved = 0;
|
||||
|
||||
if((SAISNULL(¶m->req) || !*SAPORT(¶m->req)) && param->operation != UDPASSOC) {
|
||||
return 100;
|
||||
}
|
||||
|
||||
r2 = (myrand()%1000);
|
||||
for(cur = acentry->chains; cur; cur = after){
|
||||
struct chain * sel;
|
||||
int exhausted;
|
||||
|
||||
for(cur = acentry->chains; cur; cur=cur->next){
|
||||
if(((weight = weight - cur->weight) > r2)|| done) {
|
||||
if(weight <= 0) {
|
||||
weight += 1000;
|
||||
done = 0;
|
||||
r2 = (myrand()%1000);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
sel = pickchain(param, cur, &after, &exhausted);
|
||||
/* every parent of the group is gone: connecting direct instead
|
||||
would be a way around the rule, so the request fails */
|
||||
if(exhausted) return 13;
|
||||
if(!sel) continue;
|
||||
cur = sel;
|
||||
if(cur->type != R_EXTIP && cur->type != R_HA &&
|
||||
cur->type != R_EXTPORT && cur->type != R_INTPORT) param->redirected++;
|
||||
done = 1;
|
||||
if(weight <= 0) {
|
||||
weight += 1000;
|
||||
done = 0;
|
||||
r2 = (myrand()%1000);
|
||||
}
|
||||
if(!connected){
|
||||
if(cur->type == R_EXTPORT || cur->type == R_INTPORT){
|
||||
if(cur->type == R_EXTPORT) param->extport = cur->range;
|
||||
else param->intport = cur->range;
|
||||
if(cur->next)continue;
|
||||
if(after)continue;
|
||||
return 0;
|
||||
}
|
||||
if(cur->type == R_EXTIP){
|
||||
@ -333,7 +409,7 @@ int handleredirect(struct clientparam * param, struct ace * acentry){
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if(cur->next)continue;
|
||||
if(after)continue;
|
||||
return 0;
|
||||
}
|
||||
else if(SAISNULL(&cur->addr) && !*SAPORT(&cur->addr)){
|
||||
@ -359,18 +435,18 @@ int handleredirect(struct clientparam * param, struct ace * acentry){
|
||||
if(cur->type == R_HA){
|
||||
ha = 1;
|
||||
}
|
||||
if(cur->next)continue;
|
||||
if(after)continue;
|
||||
if(!ha) return 0;
|
||||
if(param->operation == UDPASSOC) return 0;
|
||||
}
|
||||
else if(!*SAPORT(&cur->addr) && !SAISNULL(&cur->addr)) {
|
||||
uint16_t port = *SAPORT(¶m->sinsr);
|
||||
param->sinsr = cur->addr;
|
||||
chainaddr(cur, ¶m->sinsr);
|
||||
*SAPORT(¶m->sinsr) = port;
|
||||
}
|
||||
else if(SAISNULL(&cur->addr) && *SAPORT(&cur->addr)) *SAPORT(¶m->sinsr) = *SAPORT(&cur->addr);
|
||||
else {
|
||||
param->sinsr = cur->addr;
|
||||
chainaddr(cur, ¶m->sinsr);
|
||||
}
|
||||
if(param->operation == UDPASSOC){
|
||||
SOCKET s;
|
||||
@ -381,6 +457,7 @@ int handleredirect(struct clientparam * param, struct ace * acentry){
|
||||
saved = 1;
|
||||
}
|
||||
if((res = alwaysauth(param))){
|
||||
parentfail(param, cur);
|
||||
return (res >= 10)? res : 60+res;
|
||||
}
|
||||
if(ha) {
|
||||
@ -400,8 +477,14 @@ int handleredirect(struct clientparam * param, struct ace * acentry){
|
||||
}
|
||||
}
|
||||
else {
|
||||
res = (redir)?clientnegotiate(redir, param, (struct sockaddr *)&cur->addr, cur->exthost):0;
|
||||
if(res) return res;
|
||||
PROXYSOCKADDRTYPE next;
|
||||
|
||||
chainaddr(cur, &next);
|
||||
res = (redir)?clientnegotiate(redir, param, (struct sockaddr *)&next, cur->exthost):0;
|
||||
if(res) {
|
||||
parentfail(param, cur);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
redir = cur;
|
||||
param->redirtype = redir->type;
|
||||
@ -425,6 +508,7 @@ int handleredirect(struct clientparam * param, struct ace * acentry){
|
||||
|
||||
if(!connected || !redir) return 0;
|
||||
res = clientnegotiate(redir, param, (struct sockaddr *)¶m->req, param->hostname);
|
||||
if(res) parentfail(param, redir);
|
||||
if(saved){
|
||||
SOCKET s;
|
||||
|
||||
|
||||
14
src/smtpp.c
14
src/smtpp.c
@ -8,6 +8,8 @@
|
||||
|
||||
#include "proxy.h"
|
||||
|
||||
#ifdef WITH_SMTPP
|
||||
|
||||
#define RETURN(xxx) { param->res = xxx; goto CLEANRET; }
|
||||
|
||||
#ifdef WITHMAIN
|
||||
@ -342,3 +344,15 @@ struct proxydef childdef = {
|
||||
};
|
||||
#include "proxymain.c"
|
||||
#endif
|
||||
#else
|
||||
|
||||
/* Built without this proxy of its own. The command and the redirect
|
||||
naming it are the STARTTLS proxy speaking that protocol, which
|
||||
negotiates the same way and passes the session on: what "tlspr -Xsmtp"
|
||||
does, under the name a configuration already uses. */
|
||||
void * smtppchild(struct clientparam * param){
|
||||
param->starttls = S_SMTPP;
|
||||
return (void *)tlsprchild;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@ -62,6 +62,13 @@ typedef struct _3proxy_sem_s {
|
||||
#endif
|
||||
#endif
|
||||
#define MAXBANDLIMS 10
|
||||
#define MAXFAILEDPARENTS 16
|
||||
/* Parent weights are kept as a share of WEIGHTSCALE, which is what the
|
||||
weights of a group add up to. WEIGHTFUZZ is how far short of it a group may
|
||||
fall and still be taken for a whole one, a thousandth of the share: three
|
||||
weights of 333, or of .333333333, otherwise leave a remainder. */
|
||||
#define WEIGHTSCALE 1000000000u
|
||||
#define WEIGHTFUZZ 1000000u
|
||||
|
||||
#ifdef WITH_POLL
|
||||
#include <poll.h>
|
||||
@ -336,7 +343,7 @@ struct chain {
|
||||
unsigned char * exthost;
|
||||
unsigned char * extuser;
|
||||
unsigned char * extpass;
|
||||
unsigned short weight;
|
||||
unsigned weight;
|
||||
unsigned short cidr;
|
||||
/* local port range for extport/intport, first in the low half */
|
||||
uint32_t range;
|
||||
@ -759,6 +766,15 @@ struct clientparam {
|
||||
that a plugin built against an older header still finds the fields it
|
||||
knows where they were. */
|
||||
int onerequest;
|
||||
/* A STARTTLS protocol to speak before the session is wrapped, set for
|
||||
one connection rather than for the service, which is how a redirect
|
||||
and a service name standing in for a mail proxy reach tlspr. */
|
||||
PROXYSERVICE starttls;
|
||||
/* Parents which failed for this connection. A retry picks another
|
||||
member of the group instead of the same one again, and a zero weight
|
||||
member is only reached once every weighted one is in here. */
|
||||
struct chain *failedparents[MAXFAILEDPARENTS];
|
||||
int nfailedparents;
|
||||
};
|
||||
|
||||
struct filemon {
|
||||
|
||||
@ -334,7 +334,8 @@ void * tlsprchild(struct clientparam* param) {
|
||||
int lv=-1;
|
||||
char proto[PROTOLEN]="-";
|
||||
int snipos = 0;
|
||||
PROXYSERVICE stlsproto = param->clientstarttls? param->clientstarttls : param->srv->srvstarttls;
|
||||
PROXYSERVICE stlsproto = param->clientstarttls? param->clientstarttls :
|
||||
(param->starttls? param->starttls : param->srv->srvstarttls);
|
||||
|
||||
if(!param->clientstarttls && stlsproto){
|
||||
res = clistarttls(param, stlsproto);
|
||||
|
||||
160
tests/cases/parent_failover.py
Normal file
160
tests/cases/parent_failover.py
Normal file
@ -0,0 +1,160 @@
|
||||
"""parent: what happens to a group when one of its members is down.
|
||||
|
||||
A parent which fails is taken out of the random choice for the rest of the
|
||||
connection, so a retry reaches another member of the group instead of the same
|
||||
dead one again. A parent of weight 0 is the fallback of its group: it is only
|
||||
used once every weighted member has failed.
|
||||
|
||||
The number of attempts is bounded by parentretries, two by default, so each
|
||||
case here needs at most one parent to fail before a working one is reached.
|
||||
"""
|
||||
|
||||
import time
|
||||
|
||||
|
||||
def served(server, needle, since=0):
|
||||
"""How many log lines carrying needle a proxy wrote past offset since."""
|
||||
return sum(1 for line in server.output()[since:].splitlines() if needle in line)
|
||||
|
||||
|
||||
def wait_served(server, needle, count, since=0, timeout=5.0):
|
||||
"""Wait for count such lines: a session is logged once it is over, which
|
||||
is a moment after the client has its answer."""
|
||||
deadline = time.time() + timeout
|
||||
while time.time() < deadline:
|
||||
seen = served(server, needle, since)
|
||||
if seen >= count:
|
||||
return seen
|
||||
time.sleep(0.05)
|
||||
return served(server, needle, since)
|
||||
|
||||
|
||||
def run(t):
|
||||
srv = t.free_port()
|
||||
good = t.free_port()
|
||||
spare = t.free_port()
|
||||
# nothing is ever started here, so connecting to it is refused at once
|
||||
dead = t.free_port()
|
||||
|
||||
origin = t.start("failover_origin", f"""
|
||||
log
|
||||
auth iponly
|
||||
allow *
|
||||
http echo * /echo**
|
||||
httpsrv -p{srv}
|
||||
""", ports=[srv])
|
||||
|
||||
goodp = t.start("failover_good", f"""
|
||||
log
|
||||
auth iponly
|
||||
allow *
|
||||
proxy -p{good}
|
||||
""", ports=[good])
|
||||
|
||||
sparep = t.start("failover_spare", f"""
|
||||
log
|
||||
auth iponly
|
||||
allow *
|
||||
proxy -p{spare}
|
||||
""", ports=[spare])
|
||||
|
||||
fallback = t.free_port()
|
||||
idle = t.free_port()
|
||||
group = t.free_port()
|
||||
allgone = t.free_port()
|
||||
lone = t.free_port()
|
||||
|
||||
t.start("failover_client", f"""
|
||||
log
|
||||
auth iponly
|
||||
|
||||
# the only weighted parent is dead, the fallback has to take over
|
||||
flush
|
||||
allow *
|
||||
parent 1000 connect 127.0.0.1 {dead}
|
||||
parent 0 connect 127.0.0.1 {spare}
|
||||
proxy -p{fallback}
|
||||
|
||||
# the weighted parent works, so the fallback stays untouched
|
||||
flush
|
||||
allow *
|
||||
parent 1000 connect 127.0.0.1 {good}
|
||||
parent 0 connect 127.0.0.1 {spare}
|
||||
proxy -p{idle}
|
||||
|
||||
# one member of a group of two is dead: a retry must not pick it again
|
||||
flush
|
||||
allow *
|
||||
parent 500 connect 127.0.0.1 {dead}
|
||||
parent 500 connect 127.0.0.1 {good}
|
||||
proxy -p{group}
|
||||
|
||||
# nothing left to fall back to
|
||||
flush
|
||||
allow *
|
||||
parent 1000 connect 127.0.0.1 {dead}
|
||||
proxy -p{allgone}
|
||||
|
||||
# a parent of weight 0 on its own is simply the parent to use
|
||||
flush
|
||||
allow *
|
||||
parent 0 connect 127.0.0.1 {good}
|
||||
proxy -p{lone}
|
||||
""", ports=[fallback, idle, group, allgone, lone])
|
||||
|
||||
url = f"http://127.0.0.1:{srv}/echo"
|
||||
needle = f"CONNECT 127.0.0.1:{srv}"
|
||||
|
||||
# --- the fallback takes over --------------------------------------------
|
||||
mark = len(sparep.output())
|
||||
r = t.http(url, proxy=f"127.0.0.1:{fallback}")
|
||||
t.eq(200, r.status, "a dead weighted parent falls back to the parent of weight 0")
|
||||
t.eq(1, wait_served(sparep, needle, 1, mark),
|
||||
"the fallback parent carried the request")
|
||||
|
||||
# --- and only then ------------------------------------------------------
|
||||
mark = len(sparep.output())
|
||||
gmark = len(goodp.output())
|
||||
for _ in range(4):
|
||||
t.eq(200, t.http(url, proxy=f"127.0.0.1:{idle}").status,
|
||||
"a working weighted parent serves the request")
|
||||
t.eq(4, wait_served(goodp, needle, 4, gmark),
|
||||
"every request went through the weighted parent")
|
||||
t.eq(0, served(sparep, needle, mark),
|
||||
"the fallback is left alone while the weighted parent works")
|
||||
|
||||
# --- a dead member of a weighted group ----------------------------------
|
||||
# Whichever of the two the first attempt picks, the request has to end up
|
||||
# at the one that is up: the dead one is not offered to the retry again.
|
||||
gmark = len(goodp.output())
|
||||
statuses = [t.http(url, proxy=f"127.0.0.1:{group}").status for _ in range(8)]
|
||||
t.eq([200] * 8, statuses,
|
||||
"a dead member of a group never fails a request twice over")
|
||||
t.eq(8, wait_served(goodp, needle, 8, gmark),
|
||||
"all of them were carried by the member which is up")
|
||||
|
||||
# --- nothing left -------------------------------------------------------
|
||||
# With every parent of the group gone the request has to fail. Connecting
|
||||
# direct instead would be a way around the rule that asked for a parent.
|
||||
omark = len(origin.output())
|
||||
r = t.http(url, proxy=f"127.0.0.1:{allgone}")
|
||||
t.ne(200, r.status, "a request fails when every parent of the group is down")
|
||||
time.sleep(0.5)
|
||||
t.eq(0, served(origin, "/echo", omark),
|
||||
"and it is not sent direct to the origin instead")
|
||||
|
||||
# --- weight 0 on its own ------------------------------------------------
|
||||
gmark = len(goodp.output())
|
||||
t.eq(200, t.http(url, proxy=f"127.0.0.1:{lone}").status,
|
||||
"a parent of weight 0 alone is used like any other")
|
||||
t.eq(1, wait_served(goodp, needle, 1, gmark),
|
||||
"through the parent it names")
|
||||
|
||||
# --- what the parser still rejects --------------------------------------
|
||||
out = t.run_config("failover_badweight", f"""
|
||||
auth iponly
|
||||
allow *
|
||||
parent 1001 connect 127.0.0.1 {good}
|
||||
proxy -p{t.free_port()}
|
||||
""")
|
||||
t.contains(out, "bad chain weight", "a weight above 1000 is still refused")
|
||||
210
tests/cases/parent_weights.py
Normal file
210
tests/cases/parent_weights.py
Normal file
@ -0,0 +1,210 @@
|
||||
"""parent weights: the fraction notation, and what a group adds up to.
|
||||
|
||||
A weight is scanned as an integer into a share of 1000000000. A weight which
|
||||
starts with 0 or . is a fraction of one, .333 being a third; anything else is
|
||||
the old notation, thousandths, which may now carry further digits after a dot,
|
||||
so 123.456 means the same as .123456. 1.0 is the exception, read as it looks,
|
||||
and a trailing % makes a weight a percentage: 50.5% is .505 is 505.
|
||||
|
||||
The values are read back from the admin interface, which dumps the parsed
|
||||
configuration, so what is checked is the number 3proxy holds rather than the
|
||||
behaviour it happens to produce.
|
||||
"""
|
||||
|
||||
import re
|
||||
import time
|
||||
|
||||
CHAIN_WEIGHT = re.compile(
|
||||
r"parent weight[^<]*</description><value><!\[CDATA\[([0-9]+)")
|
||||
|
||||
|
||||
def weights(t, adm):
|
||||
return [int(v) for v in CHAIN_WEIGHT.findall(t.http(f"http://127.0.0.1:{adm}/S").text)]
|
||||
|
||||
|
||||
def served(server, needle, since=0):
|
||||
return sum(1 for line in server.output()[since:].splitlines() if needle in line)
|
||||
|
||||
|
||||
def wait_served(server, needle, count, since=0, timeout=5.0):
|
||||
deadline = time.time() + timeout
|
||||
while time.time() < deadline:
|
||||
seen = served(server, needle, since)
|
||||
if seen >= count:
|
||||
return seen
|
||||
time.sleep(0.05)
|
||||
return served(server, needle, since)
|
||||
|
||||
|
||||
def run(t):
|
||||
adm = t.free_port()
|
||||
prx = t.free_port()
|
||||
dummy = t.free_port()
|
||||
|
||||
t.start("weights_parse", f"""
|
||||
auth iponly
|
||||
allow *
|
||||
admin -p{adm}
|
||||
|
||||
flush
|
||||
auth iponly
|
||||
allow *
|
||||
parent 1000 connect 127.0.0.1 {dummy}
|
||||
parent 1.0 connect 127.0.0.1 {dummy}
|
||||
parent 1.00000000000000 connect 127.0.0.1 {dummy}
|
||||
parent 500 connect 127.0.0.1 {dummy}
|
||||
parent 1 connect 127.0.0.1 {dummy}
|
||||
parent 1.5 connect 127.0.0.1 {dummy}
|
||||
parent 123.456 connect 127.0.0.1 {dummy}
|
||||
parent 12.34 connect 127.0.0.1 {dummy}
|
||||
parent 1.000001 connect 127.0.0.1 {dummy}
|
||||
parent .333 connect 127.0.0.1 {dummy}
|
||||
parent 0.333 connect 127.0.0.1 {dummy}
|
||||
parent .5 connect 127.0.0.1 {dummy}
|
||||
parent .333333333 connect 127.0.0.1 {dummy}
|
||||
parent 0.000000001 connect 127.0.0.1 {dummy}
|
||||
parent 100% connect 127.0.0.1 {dummy}
|
||||
parent 50.5% connect 127.0.0.1 {dummy}
|
||||
parent 50% connect 127.0.0.1 {dummy}
|
||||
parent 33.3333333% connect 127.0.0.1 {dummy}
|
||||
parent 1.0% connect 127.0.0.1 {dummy}
|
||||
parent 0.0000001% connect 127.0.0.1 {dummy}
|
||||
parent 0 connect 127.0.0.1 {dummy}
|
||||
proxy -p{prx}
|
||||
""", ports=[adm, prx])
|
||||
|
||||
t.eq([
|
||||
1000000000, # 1000, the old notation for the whole share
|
||||
1000000000, # 1.0, the same share written as a fraction
|
||||
1000000000, # 1.00000000000000, zeroes past the point change nothing
|
||||
500000000, # 500
|
||||
1000000, # 1, a thousandth
|
||||
1500000, # 1.5, one thousandth and a half of one
|
||||
123456000, # 123.456, the old notation carried further
|
||||
12340000, # 12.34
|
||||
1000001, # 1.000001, six digits past the thousandths
|
||||
333000000, # .333
|
||||
333000000, # 0.333, the same thing written out
|
||||
500000000, # .5
|
||||
333333333, # .333333333, the finest the resolution goes
|
||||
1, # 0.000000001, one part of the whole
|
||||
1000000000, # 100%
|
||||
505000000, # 50.5%, the same as .505 and as 505
|
||||
500000000, # 50%
|
||||
333333333, # 33.3333333%, seven digits past the point
|
||||
10000000, # 1.0%, a percent rather than the whole share
|
||||
1, # 0.0000001%, one part again
|
||||
0, # the fallback weight
|
||||
], weights(t, adm), "every notation is scanned into its share")
|
||||
|
||||
# --- what the parser refuses ------------------------------------------
|
||||
for bad in ("1001", "1000.1", "1.0000001", ".1234567890", "01", "abc",
|
||||
"1.2.3", "-1", "1e9", "101%", "50.55555555%", "%", "5%%",
|
||||
"%5"):
|
||||
out = t.run_config("weights_bad", f"""
|
||||
auth iponly
|
||||
allow *
|
||||
parent {bad} connect 127.0.0.1 {dummy}
|
||||
proxy -p{t.free_port()}
|
||||
""")
|
||||
t.contains(out, "bad chain weight", f"{bad} is refused as a weight")
|
||||
|
||||
# --- a group that all but adds up ---------------------------------------
|
||||
# .999999999 is one part short of the whole share. It still closes its
|
||||
# group, so the parent after it is the next hop of a chain rather than
|
||||
# another member of the same group.
|
||||
srv = t.free_port()
|
||||
first = t.free_port()
|
||||
second = t.free_port()
|
||||
chained = t.free_port()
|
||||
grouped = t.free_port()
|
||||
thirds = t.free_port()
|
||||
|
||||
t.start("weights_origin", f"""
|
||||
log
|
||||
auth iponly
|
||||
allow *
|
||||
http echo * /echo**
|
||||
httpsrv -p{srv}
|
||||
""", ports=[srv])
|
||||
|
||||
firstp = t.start("weights_first", f"""
|
||||
log
|
||||
auth iponly
|
||||
allow *
|
||||
proxy -p{first}
|
||||
""", ports=[first])
|
||||
|
||||
secondp = t.start("weights_second", f"""
|
||||
log
|
||||
auth iponly
|
||||
allow *
|
||||
proxy -p{second}
|
||||
""", ports=[second])
|
||||
|
||||
t.start("weights_client", f"""
|
||||
log
|
||||
auth iponly
|
||||
|
||||
# one part short of the whole share still ends the group
|
||||
flush
|
||||
allow *
|
||||
parent .999999999 connect 127.0.0.1 {first}
|
||||
parent 1000 connect 127.0.0.1 {second}
|
||||
proxy -p{chained}
|
||||
|
||||
# two halves, one written each way, are one group and one hop
|
||||
flush
|
||||
allow *
|
||||
parent 500 connect 127.0.0.1 {first}
|
||||
parent .5 connect 127.0.0.1 {second}
|
||||
proxy -p{grouped}
|
||||
|
||||
# three thirds are a thousandth short of the whole share and still
|
||||
# make a group, so the parent after them is the next hop
|
||||
flush
|
||||
allow *
|
||||
parent 333 connect 127.0.0.1 {first}
|
||||
parent 333 connect 127.0.0.1 {first}
|
||||
parent 333 connect 127.0.0.1 {first}
|
||||
parent 1000 connect 127.0.0.1 {second}
|
||||
proxy -p{thirds}
|
||||
""", ports=[chained, grouped, thirds])
|
||||
|
||||
url = f"http://127.0.0.1:{srv}/echo"
|
||||
toorigin = f"CONNECT 127.0.0.1:{srv}"
|
||||
tosecond = f"CONNECT 127.0.0.1:{second}"
|
||||
|
||||
fmark, smark = len(firstp.output()), len(secondp.output())
|
||||
t.eq(200, t.http(url, proxy=f"127.0.0.1:{chained}").status,
|
||||
"a chain of two groups carries the request")
|
||||
t.eq(1, wait_served(firstp, tosecond, 1, fmark),
|
||||
"the first group's parent was asked for the second one")
|
||||
t.eq(1, wait_served(secondp, toorigin, 1, smark),
|
||||
"and the second group's parent reached the origin")
|
||||
t.eq(0, served(firstp, toorigin, fmark),
|
||||
"the first parent never went to the origin itself")
|
||||
|
||||
# both members of one group talk to the origin, never to each other
|
||||
fmark, smark = len(firstp.output()), len(secondp.output())
|
||||
for _ in range(8):
|
||||
t.eq(200, t.http(url, proxy=f"127.0.0.1:{grouped}").status,
|
||||
"a group of two halves carries the request")
|
||||
t.eq(8, wait_served(firstp, toorigin, 8, fmark, timeout=0.5) +
|
||||
wait_served(secondp, toorigin, 8, smark, timeout=0.5),
|
||||
"each request took one hop, through either half")
|
||||
t.eq(0, served(firstp, tosecond, fmark),
|
||||
"the halves are one group, not a chain")
|
||||
|
||||
# --- three thirds -------------------------------------------------------
|
||||
# 999000000 is within a thousandth of the whole share, so the group closes
|
||||
# there. Were it left open the parent of weight 1000 would join it and
|
||||
# carry about half the requests on its own, without the first hop.
|
||||
fmark, smark = len(firstp.output()), len(secondp.output())
|
||||
for _ in range(8):
|
||||
t.eq(200, t.http(url, proxy=f"127.0.0.1:{thirds}").status,
|
||||
"a group of three thirds carries the request")
|
||||
t.eq(8, wait_served(firstp, tosecond, 8, fmark),
|
||||
"every request took a third as its first hop")
|
||||
t.eq(8, wait_served(secondp, toorigin, 8, smark),
|
||||
"and the parent after them as its second")
|
||||
Loading…
Reference in New Issue
Block a user