Compare commits

..

No commits in common. "ca4667c03513666c412b3278794fbfd6ddc2d225" and "16ac797008e649f17db7da69124b78729763b76a" have entirely different histories.

27 changed files with 66 additions and 1010 deletions

View File

@ -27,7 +27,7 @@ jobs:
if: ${{ startsWith(matrix.target, 'ubuntu') }} if: ${{ startsWith(matrix.target, 'ubuntu') }}
run: sudo apt-get update && sudo apt-get install -y libssl-dev libpam-dev libpcre2-dev run: sudo apt-get update && sudo apt-get install -y libssl-dev libpam-dev libpcre2-dev
- name: make - name: make
run: make -f Makefile.Linux MAILPROXY=true FTP=true run: make -f Makefile.Linux
- name: regression tests - name: regression tests
run: python3 tests/run.py run: python3 tests/run.py
- name: mkdir - name: mkdir

View File

@ -1,85 +0,0 @@
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

View File

@ -70,8 +70,6 @@ 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_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_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_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) if(NOT WIN32 AND NOT APPLE)
option(3PROXY_STATIC_LINK "Statically link libraries using -Wl,-Bstatic (Linux/Unix only)" OFF) option(3PROXY_STATIC_LINK "Statically link libraries using -Wl,-Bstatic (Linux/Unix only)" OFF)
@ -85,13 +83,10 @@ 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_NONE "Do not build standalone binaries" OFF)
option(3PROXY_BUILD_PROXY "Build standalone proxy binary" ON) option(3PROXY_BUILD_PROXY "Build standalone proxy binary" ON)
option(3PROXY_BUILD_SOCKS "Build standalone socks binary" ON) option(3PROXY_BUILD_SOCKS "Build standalone socks binary" ON)
# The mail proxies and FTP are asked for rather than assumed: without them option(3PROXY_BUILD_POP3P "Build standalone pop3p binary" ON)
# pop3p, imapp and smtpp are the STARTTLS proxy under those names, and ftp option(3PROXY_BUILD_IMAPP "Build standalone imapp binary" ON)
# is not spoken at all. A standalone binary needs the support it is made of. option(3PROXY_BUILD_SMTPP "Build standalone smtpp binary" ON)
option(3PROXY_BUILD_POP3P "Build standalone pop3p binary" OFF) option(3PROXY_BUILD_FTPPR "Build standalone ftppr binary" ON)
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_TCPPM "Build standalone tcppm binary" ON)
option(3PROXY_BUILD_UDPPM "Build standalone udppm binary" ON) option(3PROXY_BUILD_UDPPM "Build standalone udppm binary" ON)
option(3PROXY_BUILD_TLSPR "Build standalone tlspr binary" ON) option(3PROXY_BUILD_TLSPR "Build standalone tlspr binary" ON)
@ -255,14 +250,6 @@ else()
) )
endif() 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) if(3PROXY_USE_HTTPSRV)
add_compile_definitions(WITH_HTTPSRV) add_compile_definitions(WITH_HTTPSRV)
endif() endif()
@ -697,15 +684,6 @@ foreach(PROXY_NAME proxy socks pop3p imapp smtpp ftppr tcppm udppm tlspr)
continue() continue()
endif() 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") if(PROXY_NAME STREQUAL "ftppr" OR PROXY_NAME STREQUAL "proxy")
# ftppr and proxy use ftp_obj # ftppr and proxy use ftp_obj
add_executable(${PROXY_NAME} add_executable(${PROXY_NAME}

View File

@ -24,16 +24,6 @@ LDFLAGS += $(EXTRA_LDFLAGS)
# -lpthreads may be reuiured on some platforms instead of -pthreads # -lpthreads may be reuiured on some platforms instead of -pthreads
# -ldl or -lld may be required for some platforms # -ldl or -lld may be required for some platforms
DCFLAGS ?= -fPIC 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 HTTPSRV ?= true
ifeq ($(HTTPSRV),true) ifeq ($(HTTPSRV),true)
CFLAGS += -DWITH_HTTPSRV CFLAGS += -DWITH_HTTPSRV

View File

@ -27,16 +27,6 @@ CFLAGS += $(EXTRA_CFLAGS)
LDFLAGS += $(EXTRA_LDFLAGS) LDFLAGS += $(EXTRA_LDFLAGS)
# The HTTP server serves the endpoints declared by http lines. The admin # 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. # 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 HTTPSRV ?= true
ifeq ($(HTTPSRV),true) ifeq ($(HTTPSRV),true)
CFLAGS += -DWITH_HTTPSRV CFLAGS += -DWITH_HTTPSRV

View File

@ -14,16 +14,6 @@ COUT = -o ./
LN = $(CC) LN = $(CC)
LDFLAGS = -xO3 LDFLAGS = -xO3
DCFLAGS = -fPIC 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 HTTPSRV ?= true
ifeq ($(HTTPSRV),true) ifeq ($(HTTPSRV),true)
CFLAGS += -DWITH_HTTPSRV CFLAGS += -DWITH_HTTPSRV

View File

@ -26,16 +26,6 @@ LDFLAGS += $(EXTRA_LDFLAGS)
# -lpthreads may be reuqired on some platforms instead of -pthreads # -lpthreads may be reuqired on some platforms instead of -pthreads
# -ldl or -lld may be required for some platforms # -ldl or -lld may be required for some platforms
DCFLAGS ?= -fPIC 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 HTTPSRV ?= true
ifeq ($(HTTPSRV),true) ifeq ($(HTTPSRV),true)
CFLAGS += -DWITH_HTTPSRV CFLAGS += -DWITH_HTTPSRV

View File

@ -23,16 +23,6 @@ LDFLAGS += -fno-strict-aliasing -mthreads
# makefile, including the += above and the STATIC/LIBSTATIC handling below. # makefile, including the += above and the STATIC/LIBSTATIC handling below.
CFLAGS += $(EXTRA_CFLAGS) CFLAGS += $(EXTRA_CFLAGS)
LDFLAGS += $(EXTRA_LDFLAGS) 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 HTTPSRV ?= true
ifeq ($(HTTPSRV),true) ifeq ($(HTTPSRV),true)
CFLAGS += -DWITH_HTTPSRV CFLAGS += -DWITH_HTTPSRV

View File

@ -34,8 +34,8 @@ For High/Critical patched version is released within 2 weeks
## Verifying downloads ## Verifying downloads
Release binaries and the source tarball are published with SHA256 checksums, an Release binaries are published with SHA256 checksums, an OpenPGP signature and
OpenPGP signature and a GitHub build provenance attestation. a GitHub build provenance attestation.
The release signing key is `3proxy-release-key.asc` in the root of this The release signing key is `3proxy-release-key.asc` in the root of this
repository, an RSA-4096 key: repository, an RSA-4096 key:
@ -59,18 +59,6 @@ gpg --verify SHA256SUMS-x86_64.asc SHA256SUMS-x86_64
sha256sum -c 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: RPM packages are signed, the signature is checked by rpm itself:
``` ```
@ -91,3 +79,5 @@ verified with the GitHub CLI:
gh attestation verify 3proxy-0.9.9.x86_64.rpm --owner 3proxy gh attestation verify 3proxy-0.9.9.x86_64.rpm --owner 3proxy
gh attestation verify oci://docker.io/3proxy/3proxy:latest --owner 3proxy gh attestation verify oci://docker.io/3proxy/3proxy:latest --owner 3proxy
``` ```
Windows binaries are Authenticode signed in addition to the above.

View File

@ -1942,12 +1942,10 @@ allowed traffic in megabytes (MB). nocountin allows you to set exclusions.
For name resolution and caching, use the commands nserver, nscache / nscache6, and nsrecord. For name resolution and caching, use the commands nserver, nscache / nscache6, and nsrecord.
<pre> <pre>
nserver 192.168.1.2 nserver 192.168.1.2
nserver 192.168.1.3:5353/tcp nserver 192.168.1.3:5353/tcp</pre>
nserver [2001:4860:4860::8844]</pre>
sets DNS resolvers. 192.168.1.3 will be used via TCP/5353 (instead of default UDP/53) 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. 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. If no nserver is configured, default system name resolution functions are used.
An IPv6 address has to be written in square brackets.
<pre> <pre>
nscache 65535 nscache 65535
nscache6 65535</pre> nscache6 65535</pre>

View File

@ -1993,13 +1993,11 @@ socks -p1080
nscache и nsrecord. nscache и nsrecord.
<pre> <pre>
nserver 192.168.1.2 nserver 192.168.1.2
nserver 192.168.1.3:5353/tcp nserver 192.168.1.3:5353/tcp</pre>
nserver [2001:4860:4860::8844]</pre>
указывает 3proxy какие машины следует использвоать в качестве серверов указывает 3proxy какие машины следует использвоать в качестве серверов
DNS. Сервер 192.168.1.3 будет использоваться по порту TCP/5353 (вместо дефолтного UDP/53) только при недостижимости DNS. Сервер 192.168.1.3 будет использоваться по порту TCP/5353 (вместо дефолтного UDP/53) только при недостижимости
192.168.1.2. Можно указать до 5 серверов. Если nserver не указан, будут 192.168.1.2. Можно указать до 5 серверов. Если nserver не указан, будут
использованы системные функции разрешения имен. использованы системные функции разрешения имен.
Адрес IPv6 необходимо записывать в квадратных скобках.
<pre> <pre>
nscache 65535 nscache 65535
nscache6 65535</pre> nscache6 65535</pre>

View File

@ -18,7 +18,6 @@
<a href="#PCRE FILTERING">PCRE FILTERING</a><br> <a href="#PCRE FILTERING">PCRE FILTERING</a><br>
<a href="#PCRE Commands">PCRE Commands</a><br> <a href="#PCRE Commands">PCRE Commands</a><br>
<a href="#PCRE Parameters">PCRE Parameters</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="#BUILT IN HTTP SERVER">BUILT IN HTTP SERVER</a><br>
<a href="#Operations">Operations</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> <a href="#What a rule adds to the answer">What a rule adds to the answer</a><br>
@ -115,17 +114,10 @@ handshake), may be used to redirect any TLS-based traffic
<b><br> <b><br>
auto</b> Proxy with protocol autoselection between proxy / auto</b> Proxy with protocol autoselection between proxy /
socks / tlspr <b><br> socks / tlspr <b><br>
pop3p</b> POP3 proxy (default port 110), in a build which pop3p</b> POP3 proxy (default port 110) <b><br>
has one: otherwise the service is <b>tlspr</b> speaking POP3 imapp</b> IMAPv4 proxy (default port 143) <b><br>
to negotiate STARTTLS, under this name. <b><br> smtpp</b> SMTP proxy (default port 25) <b><br>
imapp</b> IMAPv4 proxy (default port 143), or <b>tlspr</b> ftppr</b> FTP proxy (default port 21) <b><br>
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> admin</b> Web interface (default port 80) <b><br>
dnspr</b> caching DNS proxy (default port 53) <b><br> dnspr</b> caching DNS proxy (default port 53) <b><br>
tcppm</b> TCP portmapper. Destination address (DSTADDR) can tcppm</b> TCP portmapper. Destination address (DSTADDR) can
@ -500,11 +492,7 @@ experimental.</p>
Nameserver to use for name resolutions. If none specified Nameserver to use for name resolutions. If none specified
system routines for name resolution is used. Optional port system routines for name resolution is used. Optional port
number may be specified. If optional /tcp is added to IP number may be specified. If optional /tcp is added to IP
address, name resolution is performed over TCP. An IPv6 address, name resolution is performed over TCP.</p>
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> <p style="margin-left:9%; margin-top: 1em"><b>authnserver</b>
@ -836,40 +824,15 @@ grouped. Proxy inside the group is selected randomly. If few
groups are specified one proxy is randomly picked from each groups are specified one proxy is randomly picked from each
group and chain of proxies is created (that is second proxy group and chain of proxies is created (that is second proxy
connected through first one and so on). Weight is used to connected through first one and so on). Weight is used to
group proxies. A weight is a share of the whole, written group proxies. Weight is a number between 1 and 1000.
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 Weights are summed and proxies are grouped together until
the weight of the group is the whole share. A group which the weight of the group is 1000. That is: <br>
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> allow * <br>
parent 500 socks5 192.168.10.1 1080 <br> parent 500 socks5 192.168.10.1 1080 <br>
parent 500 connect 192.168.10.1 3128 <br> parent 500 connect 192.168.10.1 3128 <br>
makes 3proxy to randomly choose between 2 proxies for all makes 3proxy to randomly choose between 2 proxies for all
outgoing connections. These 2 proxies form 1 group (their outgoing connections. These 2 proxies form 1 group
weights are the whole share between them). <br> (summarized weight is 1000). <br>
allow * * * 80 <br> allow * * * 80 <br>
parent 1000 socks5 192.168.10.1 1080 <br> parent 1000 socks5 192.168.10.1 1080 <br>
parent 1000 connect 192.168.20.1 3128 <br> parent 1000 connect 192.168.20.1 3128 <br>
@ -1009,36 +972,13 @@ Changes the external address for a given connection to
1.2.3.4 (equivalent to <b>-e1.2.3.4</b>) <br> 1.2.3.4 (equivalent to <b>-e1.2.3.4</b>) <br>
Optional username and password are used to authenticate on Optional username and password are used to authenticate on
parent proxy. Username of &acute;*&acute; means username parent proxy. Username of &acute;*&acute; means username
must be supplied by user. <br> must be supplied by user.</p>
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> <p style="margin-left:9%; margin-top: 1em"><b>parentretries</b>
<i>&lt;number&gt;</i> <br> <i>&lt;number&gt;</i> <br>
Number of attempts to reach a parent proxy. Default is 2. Number of retries to connect to parent proxy. Default is
Each attempt picks a parent again, leaving out the ones 1.</p>
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> <p style="margin-left:9%; margin-top: 1em"><b>nolog</b>
<i>&lt;n&gt;</i> <br> <i>&lt;n&gt;</i> <br>
@ -1576,29 +1516,6 @@ the connection data. Warning: Regular expressions
don&rsquo;t require authentication and cannot replace don&rsquo;t require authentication and cannot replace
authentication and/or allow/deny ACLs.</p> 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 <h2>BUILT IN HTTP SERVER
<a name="BUILT IN HTTP SERVER"></a> <a name="BUILT IN HTTP SERVER"></a>
</h2> </h2>

View File

@ -95,19 +95,16 @@ SNI proxy (destination address is taken from TLS handshake), may be used to redi
Proxy with protocol autoselection between proxy / socks / tlspr Proxy with protocol autoselection between proxy / socks / tlspr
.br .br
.B pop3p .B pop3p
POP3 proxy (default port 110), in a build which has one: otherwise the POP3 proxy (default port 110)
service is \fBtlspr\fR speaking POP3 to negotiate STARTTLS, under this name.
.br .br
.B imapp .B imapp
IMAPv4 proxy (default port 143), or \fBtlspr\fR speaking IMAP, as above. IMAPv4 proxy (default port 143)
.br .br
.B smtpp .B smtpp
SMTP proxy (default port 25), or \fBtlspr\fR speaking SMTP, as above. SMTP proxy (default port 25)
.br .br
.B ftppr .B ftppr
FTP proxy (default port 21), in a build with FTP support. Without it the FTP proxy (default port 21)
service is known but answers nothing, and \fBftp://\fR is not a URL the HTTP
proxy fetches.
.br .br
.B admin .B admin
Web interface (default port 80) Web interface (default port 80)
@ -511,13 +508,6 @@ system routines for name resolution is
used. Optional port number may be specified. used. Optional port number may be specified.
If optional /tcp is added to IP address, name resolution is If optional /tcp is added to IP address, name resolution is
performed over TCP. 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
.BR authnserver .BR authnserver
@ -870,35 +860,9 @@ build proxy chain. Proxies may be grouped. Proxy inside the
group is selected randomly. If few groups are specified one proxy group is selected randomly. If few groups are specified one proxy
is randomly picked from each group and chain of proxies is created is randomly picked from each group and chain of proxies is created
(that is second proxy connected through first one and so on). (that is second proxy connected through first one and so on).
Weight is used to group proxies. A weight is a share of the whole, written Weight is used to group proxies. Weight is a number between 1 and 1000.
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 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 the group is 1000. That is:
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 .br
allow * allow *
.br .br
@ -907,8 +871,7 @@ evenly needs no adjusting by hand. That is:
parent 500 connect 192.168.10.1 3128 parent 500 connect 192.168.10.1 3128
.br .br
makes 3proxy to randomly choose between 2 proxies for all outgoing makes 3proxy to randomly choose between 2 proxies for all outgoing
connections. These 2 proxies form 1 group (their weights are the whole share connections. These 2 proxies form 1 group (summarized weight is 1000).
between them).
.br .br
allow * * * 80 allow * * * 80
.br .br
@ -1041,37 +1004,12 @@ local HTTP proxy parses requests and allows only GET and POST requests.
.br .br
Optional username and password are used to authenticate on parent Optional username and password are used to authenticate on parent
proxy. Username of \'*\' means username must be supplied by user. 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
.BR parentretries .BR parentretries
\fI<number>\fR \fI<number>\fR
.br .br
Number of attempts to reach a parent proxy. Default is 2. Each attempt Number of retries to connect to parent proxy. Default is 1.
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 .br
@ -1635,21 +1573,6 @@ matched if the ACL matches the connection data.
Warning: Regular expressions don't require authentication and cannot replace Warning: Regular expressions don't require authentication and cannot replace
authentication and/or allow/deny ACLs. 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 .SH BUILT IN HTTP SERVER
The \fBhttpsrv\fR service answers requests itself instead of forwarding them. 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 What it does with a request is decided by \fBhttp\fR rules, which are taken in

View File

@ -2,7 +2,7 @@
# 3 proxy common Makefile # 3 proxy common Makefile
# #
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 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
sockmap$(OBJSUFFICS): sockmap.c proxy.h structures.h sockmap$(OBJSUFFICS): sockmap.c proxy.h structures.h
$(CC) $(CFLAGS) sockmap.c $(CC) $(CFLAGS) sockmap.c

View File

@ -903,82 +903,6 @@ static int parserange(unsigned char *arg, uint32_t *range)
return 0; 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){ static int h_parent(int argc, unsigned char **argv){
struct ace *acl = NULL; struct ace *acl = NULL;
struct chain *chains; struct chain *chains;
@ -998,10 +922,9 @@ static int h_parent(int argc, unsigned char **argv){
return(21); return(21);
} }
memset(chains, 0, sizeof(struct chain)); memset(chains, 0, sizeof(struct chain));
/* 0 is the fallback weight: such a parent is only used once every chains->weight = (unsigned)atoi((char *)argv[1]);
weighted parent of its group has failed */ if(chains->weight == 0 || chains->weight >1000) {
if(parseweight(argv[1], &chains->weight)) { fprintf(stderr, "Chaining error: bad chain weight %u line %d\n", chains->weight, linenum);
fprintf(stderr, "Chaining error: bad chain weight %s line %d\n", argv[1], linenum);
free(chains); free(chains);
return(3); return(3);
} }
@ -1574,7 +1497,7 @@ static int h_ace(int argc, unsigned char **argv){
return 5; return 5;
} }
*SAPORT(&acl->chains->addr) = htons((uint16_t)atoi((char *)argv[2])); *SAPORT(&acl->chains->addr) = htons((uint16_t)atoi((char *)argv[2]));
acl->chains->weight = WEIGHTSCALE; acl->chains->weight = 1000;
case ALLOW: case ALLOW:
case DENY: case DENY:
if(!conf.acl){ if(!conf.acl){

View File

@ -802,7 +802,7 @@ static struct property prop_pwlist[] = {
static struct property prop_chain[] = { static struct property prop_chain[] = {
{"addr", ef_chain_addr, TYPE_SA, "parent address"}, {"addr", ef_chain_addr, TYPE_SA, "parent address"},
{"type", ef_chain_type, TYPE_STRING, "parent type"}, {"type", ef_chain_type, TYPE_STRING, "parent type"},
{"weight", ef_chain_weight, TYPE_INTEGER, "parent weight, 1000000000 is the whole share, 0 is a fallback"}, {"weight", ef_chain_weight, TYPE_SHORT, "parent weight 0-1000"},
{"user", ef_chain_user, TYPE_STRING, "parent login"}, {"user", ef_chain_user, TYPE_STRING, "parent login"},
{"password", ef_chain_password, TYPE_PASSWORD, "parent password"}, {"password", ef_chain_password, TYPE_PASSWORD, "parent password"},
{"secure", ef_chain_secure, TYPE_INTEGER, "secure mode"}, {"secure", ef_chain_secure, TYPE_INTEGER, "secure mode"},

View File

@ -7,8 +7,6 @@
#include "proxy.h" #include "proxy.h"
#ifdef WITH_FTP
/* /*
* Read one FTP server response, skipping continuation lines (lines whose * Read one FTP server response, skipping continuation lines (lines whose
* 4th character is '-' per RFC 959). Returns the line length on success, * 4th character is '-' per RFC 959). Returns the line length on success,
@ -251,5 +249,3 @@ SOCKET ftpcommand(struct clientparam *param, unsigned char * command, unsigned c
} }
return s; return s;
} }
#endif

View File

@ -8,8 +8,6 @@
#include "proxy.h" #include "proxy.h"
#ifdef WITH_FTP
#define RETURN(xxx) { param->res = xxx; goto CLEANRET; } #define RETURN(xxx) { param->res = xxx; goto CLEANRET; }
#define BUFSIZE 2048 #define BUFSIZE 2048
@ -347,16 +345,3 @@ struct proxydef childdef = {
}; };
#include "proxymain.c" #include "proxymain.c"
#endif #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

View File

@ -8,8 +8,6 @@
#include "proxy.h" #include "proxy.h"
#ifdef WITH_IMAPP
#define RETURN(xxx) { param->res = xxx; goto CLEANRET; } #define RETURN(xxx) { param->res = xxx; goto CLEANRET; }
#define CL_LOGINCMD 0 #define CL_LOGINCMD 0
@ -252,15 +250,3 @@ struct proxydef childdef = {
}; };
#include "proxymain.c" #include "proxymain.c"
#endif #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

View File

@ -8,8 +8,6 @@
#include "proxy.h" #include "proxy.h"
#ifdef WITH_POP3P
#define RETURN(xxx) { param->res = xxx; goto CLEANRET; } #define RETURN(xxx) { param->res = xxx; goto CLEANRET; }
#ifdef WITHMAIN #ifdef WITHMAIN
@ -90,15 +88,3 @@ struct proxydef childdef = {
}; };
#include "proxymain.c" #include "proxymain.c"
#endif #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

View File

@ -385,12 +385,10 @@ for(;;){
if (!strncasecmp((char *)sb, "http://", 7)) { if (!strncasecmp((char *)sb, "http://", 7)) {
sb += 7; sb += 7;
} }
#ifdef WITH_FTP
else if (!strncasecmp((char *)sb, "ftp://", 6)) { else if (!strncasecmp((char *)sb, "ftp://", 6)) {
ftp = 1; ftp = 1;
sb += 6; sb += 6;
} }
#endif
else if(*sb == '/') { else if(*sb == '/') {
param->transparent = 1; param->transparent = 1;
} }
@ -714,7 +712,6 @@ for(;;){
#endif #endif
#ifdef WITH_FTP
if(ftp && param->redirtype != R_HTTP){ if(ftp && param->redirtype != R_HTTP){
SOCKET s; SOCKET s;
int mode = 0; int mode = 0;
@ -966,7 +963,6 @@ for(;;){
} }
RETURN(res); RETURN(res);
} }
#endif
if(isconnect && param->redirtype != R_HTTP) { if(isconnect && param->redirtype != R_HTTP) {
if(param->redirectfunc) { if(param->redirectfunc) {

View File

@ -273,121 +273,45 @@ 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 handleredirect(struct clientparam * param, struct ace * acentry){
int connected = 0; int connected = 0;
int weight = 1000;
int res; int res;
int done = 0;
int ha = 0; int ha = 0;
struct chain * cur; struct chain * cur;
struct chain * after;
struct chain * redir = NULL; struct chain * redir = NULL;
int r2;
int saved = 0; int saved = 0;
if((SAISNULL(&param->req) || !*SAPORT(&param->req)) && param->operation != UDPASSOC) { if((SAISNULL(&param->req) || !*SAPORT(&param->req)) && param->operation != UDPASSOC) {
return 100; return 100;
} }
for(cur = acentry->chains; cur; cur = after){ r2 = (myrand()%1000);
struct chain * sel;
int exhausted;
sel = pickchain(param, cur, &after, &exhausted); for(cur = acentry->chains; cur; cur=cur->next){
/* every parent of the group is gone: connecting direct instead if(((weight = weight - cur->weight) > r2)|| done) {
would be a way around the rule, so the request fails */ if(weight <= 0) {
if(exhausted) return 13; weight += 1000;
if(!sel) continue; done = 0;
cur = sel; r2 = (myrand()%1000);
}
continue;
}
if(cur->type != R_EXTIP && cur->type != R_HA && if(cur->type != R_EXTIP && cur->type != R_HA &&
cur->type != R_EXTPORT && cur->type != R_INTPORT) param->redirected++; 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(!connected){
if(cur->type == R_EXTPORT || cur->type == R_INTPORT){ if(cur->type == R_EXTPORT || cur->type == R_INTPORT){
if(cur->type == R_EXTPORT) param->extport = cur->range; if(cur->type == R_EXTPORT) param->extport = cur->range;
else param->intport = cur->range; else param->intport = cur->range;
if(after)continue; if(cur->next)continue;
return 0; return 0;
} }
if(cur->type == R_EXTIP){ if(cur->type == R_EXTIP){
@ -409,7 +333,7 @@ int handleredirect(struct clientparam * param, struct ace * acentry){
} }
} }
#endif #endif
if(after)continue; if(cur->next)continue;
return 0; return 0;
} }
else if(SAISNULL(&cur->addr) && !*SAPORT(&cur->addr)){ else if(SAISNULL(&cur->addr) && !*SAPORT(&cur->addr)){
@ -435,18 +359,18 @@ int handleredirect(struct clientparam * param, struct ace * acentry){
if(cur->type == R_HA){ if(cur->type == R_HA){
ha = 1; ha = 1;
} }
if(after)continue; if(cur->next)continue;
if(!ha) return 0; if(!ha) return 0;
if(param->operation == UDPASSOC) return 0; if(param->operation == UDPASSOC) return 0;
} }
else if(!*SAPORT(&cur->addr) && !SAISNULL(&cur->addr)) { else if(!*SAPORT(&cur->addr) && !SAISNULL(&cur->addr)) {
uint16_t port = *SAPORT(&param->sinsr); uint16_t port = *SAPORT(&param->sinsr);
chainaddr(cur, &param->sinsr); param->sinsr = cur->addr;
*SAPORT(&param->sinsr) = port; *SAPORT(&param->sinsr) = port;
} }
else if(SAISNULL(&cur->addr) && *SAPORT(&cur->addr)) *SAPORT(&param->sinsr) = *SAPORT(&cur->addr); else if(SAISNULL(&cur->addr) && *SAPORT(&cur->addr)) *SAPORT(&param->sinsr) = *SAPORT(&cur->addr);
else { else {
chainaddr(cur, &param->sinsr); param->sinsr = cur->addr;
} }
if(param->operation == UDPASSOC){ if(param->operation == UDPASSOC){
SOCKET s; SOCKET s;
@ -457,7 +381,6 @@ int handleredirect(struct clientparam * param, struct ace * acentry){
saved = 1; saved = 1;
} }
if((res = alwaysauth(param))){ if((res = alwaysauth(param))){
parentfail(param, cur);
return (res >= 10)? res : 60+res; return (res >= 10)? res : 60+res;
} }
if(ha) { if(ha) {
@ -477,14 +400,8 @@ int handleredirect(struct clientparam * param, struct ace * acentry){
} }
} }
else { else {
PROXYSOCKADDRTYPE next; res = (redir)?clientnegotiate(redir, param, (struct sockaddr *)&cur->addr, cur->exthost):0;
if(res) return res;
chainaddr(cur, &next);
res = (redir)?clientnegotiate(redir, param, (struct sockaddr *)&next, cur->exthost):0;
if(res) {
parentfail(param, cur);
return res;
}
} }
redir = cur; redir = cur;
param->redirtype = redir->type; param->redirtype = redir->type;
@ -508,7 +425,6 @@ int handleredirect(struct clientparam * param, struct ace * acentry){
if(!connected || !redir) return 0; if(!connected || !redir) return 0;
res = clientnegotiate(redir, param, (struct sockaddr *)&param->req, param->hostname); res = clientnegotiate(redir, param, (struct sockaddr *)&param->req, param->hostname);
if(res) parentfail(param, redir);
if(saved){ if(saved){
SOCKET s; SOCKET s;

View File

@ -8,8 +8,6 @@
#include "proxy.h" #include "proxy.h"
#ifdef WITH_SMTPP
#define RETURN(xxx) { param->res = xxx; goto CLEANRET; } #define RETURN(xxx) { param->res = xxx; goto CLEANRET; }
#ifdef WITHMAIN #ifdef WITHMAIN
@ -344,15 +342,3 @@ struct proxydef childdef = {
}; };
#include "proxymain.c" #include "proxymain.c"
#endif #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

View File

@ -62,13 +62,6 @@ typedef struct _3proxy_sem_s {
#endif #endif
#endif #endif
#define MAXBANDLIMS 10 #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 #ifdef WITH_POLL
#include <poll.h> #include <poll.h>
@ -343,7 +336,7 @@ struct chain {
unsigned char * exthost; unsigned char * exthost;
unsigned char * extuser; unsigned char * extuser;
unsigned char * extpass; unsigned char * extpass;
unsigned weight; unsigned short weight;
unsigned short cidr; unsigned short cidr;
/* local port range for extport/intport, first in the low half */ /* local port range for extport/intport, first in the low half */
uint32_t range; uint32_t range;
@ -766,15 +759,6 @@ struct clientparam {
that a plugin built against an older header still finds the fields it that a plugin built against an older header still finds the fields it
knows where they were. */ knows where they were. */
int onerequest; 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 { struct filemon {

View File

@ -334,8 +334,7 @@ void * tlsprchild(struct clientparam* param) {
int lv=-1; int lv=-1;
char proto[PROTOLEN]="-"; char proto[PROTOLEN]="-";
int snipos = 0; int snipos = 0;
PROXYSERVICE stlsproto = param->clientstarttls? param->clientstarttls : PROXYSERVICE stlsproto = param->clientstarttls? param->clientstarttls : param->srv->srvstarttls;
(param->starttls? param->starttls : param->srv->srvstarttls);
if(!param->clientstarttls && stlsproto){ if(!param->clientstarttls && stlsproto){
res = clistarttls(param, stlsproto); res = clistarttls(param, stlsproto);

View File

@ -1,160 +0,0 @@
"""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")

View File

@ -1,210 +0,0 @@
"""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")