Compare commits

..

No commits in common. "ea4b2cc3a2fe141aeeed2face032a6efcae02b8a" and "69c6ddc8c47920226169fe834cee9850c6fd4b2b" have entirely different histories.

32 changed files with 213 additions and 1131 deletions

View File

@ -2,7 +2,6 @@ name: Update HTML documentation
on: on:
push: push:
branches: [master]
paths: paths:
- 'man/**' - 'man/**'
workflow_dispatch: workflow_dispatch:

View File

@ -4,7 +4,6 @@ on:
push: push:
paths: paths:
- RELEASE - RELEASE
- DEVEL
workflow_dispatch: workflow_dispatch:
permissions: permissions:
@ -20,23 +19,12 @@ jobs:
- name: Regenerate version files - name: Regenerate version files
run: | run: |
# A release branch carries RELEASE, a development branch DEVEL, whose RELEASE=$(tr -d ' \t\r\n' < RELEASE)
# version may have a suffix such as 2.0.0-devel. if [ -z "$RELEASE" ]; then echo "RELEASE is empty"; exit 1; fi
if [ -f RELEASE ]; then VERFILE=RELEASE MAJOR=$(echo "$RELEASE" | cut -d . -f 1)
elif [ -f DEVEL ]; then VERFILE=DEVEL SUBMAJOR=$(echo "$RELEASE" | cut -d . -f 2)
else echo "neither RELEASE nor DEVEL is present"; exit 1 MINOR=$(echo "$RELEASE" | cut -d . -f 3)
fi SUBMINOR=$(echo "$RELEASE" | cut -d . -f 4)
RELEASE=$(tr -d ' \t\r\n' < "$VERFILE")
if [ -z "$RELEASE" ]; then echo "$VERFILE is empty"; exit 1; fi
# the numbered fields take the numeric part, the strings keep all of it
NUMBERS=${RELEASE%%[!0-9.]*}
NUMBERS=${NUMBERS%.}
if [ -z "$NUMBERS" ]; then echo "no version number in '$RELEASE'"; exit 1; fi
MAJOR=$(echo "$NUMBERS" | cut -d . -f 1)
SUBMAJOR=$(echo "$NUMBERS" | cut -d . -f 2)
MINOR=$(echo "$NUMBERS" | cut -d . -f 3)
SUBMINOR=$(echo "$NUMBERS" | cut -d . -f 4)
[ -n "$SUBMAJOR" ] || SUBMAJOR=0 [ -n "$SUBMAJOR" ] || SUBMAJOR=0
[ -n "$MINOR" ] || MINOR=0 [ -n "$MINOR" ] || MINOR=0
[ -n "$SUBMINOR" ] || SUBMINOR=0 [ -n "$SUBMINOR" ] || SUBMINOR=0
@ -51,10 +39,7 @@ jobs:
else else
BUILDDATE=$(date -u +%y%m%d%H%M%S) BUILDDATE=$(date -u +%y%m%d%H%M%S)
fi fi
# rpm refuses a hyphen in Version, so a suffix becomes a tilde, which echo "release $RELEASE -> $MAJOR $SUBMAJOR $MINOR $SUBMINOR, build date $BUILDDATE"
# also sorts before the release of the same number
RPMVERSION=$(echo "$RELEASE" | tr '-' '~')
echo "$VERFILE $RELEASE -> $MAJOR $SUBMAJOR $MINOR $SUBMINOR, rpm $RPMVERSION, build date $BUILDDATE"
SOURCE_DATE_EPOCH=$(date -u -d "20${BUILDDATE:0:2}-${BUILDDATE:2:2}-${BUILDDATE:4:2} ${BUILDDATE:6:2}:${BUILDDATE:8:2}:${BUILDDATE:10:2}" +%s) SOURCE_DATE_EPOCH=$(date -u -d "20${BUILDDATE:0:2}-${BUILDDATE:2:2}-${BUILDDATE:4:2} ${BUILDDATE:6:2}:${BUILDDATE:8:2}:${BUILDDATE:10:2}" +%s)
YEAR=$(date -u -d @$SOURCE_DATE_EPOCH +%Y) YEAR=$(date -u -d @$SOURCE_DATE_EPOCH +%Y)
@ -74,7 +59,7 @@ jobs:
mv scripts/rh/3proxy.spec scripts/rh/3proxy.spec.old mv scripts/rh/3proxy.spec scripts/rh/3proxy.spec.old
{ echo "Name: 3proxy" { echo "Name: 3proxy"
echo "Version: $RPMVERSION" echo "Version: $RELEASE"
echo "Release: 1%{?dist}" echo "Release: 1%{?dist}"
tail --lines=+4 scripts/rh/3proxy.spec.old tail --lines=+4 scripts/rh/3proxy.spec.old
} > scripts/rh/3proxy.spec } > scripts/rh/3proxy.spec
@ -105,5 +90,5 @@ jobs:
git config user.name "github-actions[bot]" git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com" git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add debian/changelog scripts/rh/3proxy.spec src/version.h git add debian/changelog scripts/rh/3proxy.spec src/version.h
git commit -m "Update version files for $(tr -d ' \t\r\n' < $(test -f RELEASE && echo RELEASE || echo DEVEL))" git commit -m "Update version files for $(tr -d ' \t\r\n' < RELEASE)"
git push git push

View File

@ -2,7 +2,6 @@ name: Update wiki
on: on:
push: push:
branches: [master]
paths: paths:
- 'doc/html/**' - 'doc/html/**'
workflow_run: workflow_run:
@ -19,8 +18,6 @@ concurrency:
jobs: jobs:
wiki: wiki:
# the wiki mirrors master, so a run started by anything else is not for it
if: github.event_name != 'workflow_run' || github.event.workflow_run.head_branch == 'master'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v7 - uses: actions/checkout@v7

View File

@ -4,20 +4,8 @@
cmake_minimum_required(VERSION 3.16) cmake_minimum_required(VERSION 3.16)
# Read the version. A release branch carries RELEASE, a development branch # Read version from RELEASE file
# DEVEL, whose version may have a suffix that project() will not take. file(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/RELEASE" PROJECT_VERSION LIMIT_COUNT 1)
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/RELEASE")
file(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/RELEASE" PROJECT_VERSION_FULL LIMIT_COUNT 1)
elseif(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/DEVEL")
file(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/DEVEL" PROJECT_VERSION_FULL LIMIT_COUNT 1)
else()
message(FATAL_ERROR "Neither RELEASE nor DEVEL found: cannot tell the version")
endif()
string(STRIP "${PROJECT_VERSION_FULL}" PROJECT_VERSION_FULL)
string(REGEX MATCH "^[0-9]+(\\.[0-9]+)*" PROJECT_VERSION "${PROJECT_VERSION_FULL}")
if(NOT PROJECT_VERSION)
message(FATAL_ERROR "No version number in '${PROJECT_VERSION_FULL}'")
endif()
project(3proxy project(3proxy
VERSION ${PROJECT_VERSION} VERSION ${PROJECT_VERSION}
@ -67,7 +55,6 @@ option(3PROXY_USE_SPLICE "Build Linux splice() support, slower than read/write f
option(3PROXY_USE_POLL "Use poll() instead of select() (Unix only)" ON) option(3PROXY_USE_POLL "Use poll() instead of select() (Unix only)" ON)
option(3PROXY_USE_WSAPOLL "Use WSAPoll instead of select() (Windows only)" ON) option(3PROXY_USE_WSAPOLL "Use WSAPoll instead of select() (Windows only)" ON)
option(3PROXY_USE_NETFILTER "Enable Linux netfilter support (Linux only)" ON) 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_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)
@ -115,9 +102,6 @@ set(CMAKE_POSITION_INDEPENDENT_CODE ON)
if(WIN32) if(WIN32)
# Windows-specific configuration # Windows-specific configuration
add_compile_definitions( add_compile_definitions(
# Windows does not take a recv and a send on one socket from two
# threads, so a UDP service answers from a socket of its own
NO_SHARE_UDP_SOCKET
WIN32 WIN32
_WIN32 _WIN32
_MBCS _MBCS
@ -209,6 +193,7 @@ elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
set(DEFAULT_PLUGINS set(DEFAULT_PLUGINS
StringsPlugin StringsPlugin
TrafficPlugin TrafficPlugin
TransparentPlugin
FilePlugin FilePlugin
) )
@ -230,6 +215,7 @@ elseif(CMAKE_SYSTEM_NAME MATCHES "FreeBSD|Darwin|OpenBSD|NetBSD")
set(DEFAULT_PLUGINS set(DEFAULT_PLUGINS
StringsPlugin StringsPlugin
TrafficPlugin TrafficPlugin
TransparentPlugin
FilePlugin FilePlugin
) )
@ -246,6 +232,7 @@ else()
set(DEFAULT_PLUGINS set(DEFAULT_PLUGINS
StringsPlugin StringsPlugin
TrafficPlugin TrafficPlugin
TransparentPlugin
FilePlugin FilePlugin
) )
endif() endif()
@ -254,25 +241,6 @@ if(3PROXY_USE_HTTPSRV)
add_compile_definitions(WITH_HTTPSRV) add_compile_definitions(WITH_HTTPSRV)
endif() endif()
# Transparent proxying needs a redirection that leaves the original
# destination where 3proxy reads it: the kernel on Linux, the socket on the
# BSDs. That means netfilter, OpenBSD divert-to or FreeBSD ipfw fwd. NetBSD
# and macOS rewrite the destination instead and are left out.
if(3PROXY_USE_TRANSPARENT AND (CMAKE_SYSTEM_NAME STREQUAL "Linux"
OR CMAKE_SYSTEM_NAME MATCHES "FreeBSD|OpenBSD|NetBSD"))
add_compile_definitions(WITH_TRANSPARENT)
set(3PROXY_TRANSPARENT_BUILT ON)
# pf keeps the original destination in its state table, which is read
# through /dev/pf. macOS has the device but ships no header for it.
include(CheckIncludeFiles)
check_include_files("sys/types.h;sys/socket.h;net/if.h;net/pfvar.h" HAVE_PFVAR_H)
if(HAVE_PFVAR_H)
add_compile_definitions(WITH_PF)
endif()
else()
set(3PROXY_TRANSPARENT_BUILT OFF)
endif()
# Unix domain sockets off: NO_UN also undefines WITH_UN if it arrives from # Unix domain sockets off: NO_UN also undefines WITH_UN if it arrives from
# elsewhere, e.g. CFLAGS # elsewhere, e.g. CFLAGS
if(NOT 3PROXY_USE_UNIX_SOCKETS) if(NOT 3PROXY_USE_UNIX_SOCKETS)
@ -503,10 +471,6 @@ if(PCRE2_FOUND)
target_sources(3proxy PRIVATE src/pcre.c) target_sources(3proxy PRIVATE src/pcre.c)
endif() endif()
if(3PROXY_TRANSPARENT_BUILT)
target_sources(3proxy PRIVATE src/transparent.c)
endif()
target_include_directories(3proxy PRIVATE target_include_directories(3proxy PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src ${CMAKE_CURRENT_SOURCE_DIR}/src
${CMAKE_CURRENT_SOURCE_DIR}/src/libs ${CMAKE_CURRENT_SOURCE_DIR}/src/libs
@ -953,7 +917,7 @@ endif()
# Summary # Summary
message(STATUS "") message(STATUS "")
message(STATUS "3proxy configuration summary:") message(STATUS "3proxy configuration summary:")
message(STATUS " Version: ${PROJECT_VERSION_FULL}") message(STATUS " Version: ${PROJECT_VERSION}")
message(STATUS " Platform: ${CMAKE_SYSTEM_NAME}") message(STATUS " Platform: ${CMAKE_SYSTEM_NAME}")
message(STATUS " Compiler: ${CMAKE_C_COMPILER_ID} ${CMAKE_C_COMPILER_VERSION}") message(STATUS " Compiler: ${CMAKE_C_COMPILER_ID} ${CMAKE_C_COMPILER_VERSION}")
message(STATUS " Build type: ${CMAKE_BUILD_TYPE}") message(STATUS " Build type: ${CMAKE_BUILD_TYPE}")
@ -963,7 +927,6 @@ message(STATUS " BUILD_SHARED: ${3PROXY_BUILD_SHARED}")
message(STATUS " USE_WOLFSSL: ${3PROXY_USE_WOLFSSL}") message(STATUS " USE_WOLFSSL: ${3PROXY_USE_WOLFSSL}")
message(STATUS " USE_OPENSSL: ${3PROXY_USE_OPENSSL}") message(STATUS " USE_OPENSSL: ${3PROXY_USE_OPENSSL}")
message(STATUS " USE_PCRE2: ${3PROXY_USE_PCRE2}") message(STATUS " USE_PCRE2: ${3PROXY_USE_PCRE2}")
message(STATUS " TRANSPARENT: ${3PROXY_TRANSPARENT_BUILT}")
message(STATUS " USE_PAM: ${3PROXY_USE_PAM}") message(STATUS " USE_PAM: ${3PROXY_USE_PAM}")
message(STATUS " USE_ODBC: ${3PROXY_USE_ODBC}") message(STATUS " USE_ODBC: ${3PROXY_USE_ODBC}")
message(STATUS " USE_POLL: ${3PROXY_USE_POLL}") message(STATUS " USE_POLL: ${3PROXY_USE_POLL}")

View File

@ -44,20 +44,7 @@ AFTERCLEAN = (find . -type f -name "*.o" -delete && find src/ -type f -name "Mak
TYPECOMMAND = cat TYPECOMMAND = cat
COMPATLIBS = COMPATLIBS =
MAKEFILE = Makefile.FreeBSD MAKEFILE = Makefile.FreeBSD
PLUGINS ?= StringsPlugin TrafficPlugin FilePlugin PLUGINS ?= StringsPlugin TrafficPlugin TransparentPlugin FilePlugin
# Transparent proxying, built in. The destination of a redirected connection
# comes from pf where its headers are available, and from the socket where a
# redirection leaves it there (OpenBSD divert-to, FreeBSD ipfw fwd). macOS
# has /dev/pf but ships no pfvar.h, so only the socket route is built there
# and no macOS redirection leaves the address on the socket.
CFLAGS += -DWITH_TRANSPARENT
TRANSPARENT_OBJS = transparent$(OBJSUFFICS)
PF_CHECK ?= $(shell printf "\#include <sys/types.h>\\n\#include <sys/socket.h>\\n\#include <net/if.h>\\n\#include <net/pfvar.h>\\n int main(){return 0;}" | tr -d \\\\ | $(CC) -x c $(CFLAGS) -o testpf.o -c - 2>/dev/null && rm testpf.o && echo true||echo false)
ifeq ($(PF_CHECK), true)
CFLAGS += -DWITH_PF
endif
ifeq ($(STATIC), true) ifeq ($(STATIC), true)
LDFLAGS += -static LDFLAGS += -static
CFLAGS += -DNOPLUGINS -DNOSTDRESOLVE -DNOCRYPT CFLAGS += -DNOPLUGINS -DNOSTDRESOLVE -DNOCRYPT

View File

@ -50,12 +50,8 @@ MAKEFILE = Makefile.Linux
# PamAuth requires libpam, you may require pam-devel package to be installed # PamAuth requires libpam, you may require pam-devel package to be installed
# SSLPlugin requires -lcrypto -lssl # SSLPlugin requires -lcrypto -lssl
#LIBS = -lcrypto -lssl -ldl #LIBS = -lcrypto -lssl -ldl
#PLUGINS = StringsPlugin TrafficPlugin PamAuth LdapPlugin #PLUGINS = SSLPlugin StringsPlugin TrafficPlugin PCREPlugin TransparentPlugin PamAuth
PLUGINS ?= StringsPlugin TrafficPlugin FilePlugin PLUGINS ?= StringsPlugin TrafficPlugin TransparentPlugin FilePlugin
# Transparent proxying, built in: it needs the packet filter of the platform
CFLAGS += -DWITH_TRANSPARENT
TRANSPARENT_OBJS = transparent$(OBJSUFFICS)
ifeq ($(STATIC), true) ifeq ($(STATIC), true)
LDFLAGS += -static LDFLAGS += -static
CFLAGS += -DNOPLUGINS -DNOSTDRESOLVE -DNOCRYPT CFLAGS += -DNOPLUGINS -DNOSTDRESOLVE -DNOCRYPT

View File

@ -34,7 +34,7 @@ AFTERCLEAN = (find . -type f -name "*.o" -delete && find src/ -type f -name "Mak
TYPECOMMAND = cat TYPECOMMAND = cat
COMPATLIBS = COMPATLIBS =
MAKEFILE = Makefile.Solaris MAKEFILE = Makefile.Solaris
PLUGINS = StringsPlugin TrafficPlugin FilePlugin PLUGINS = StringsPlugin TrafficPlugin TransparentPlugin FilePlugin
WOLFSSL_CHECK = $(shell printf "\#include <wolfssl/options.h>\\n\#include <wolfssl/openssl/ssl.h>\\n int main(){return 0;}" | tr -d \\\\ | $(CC) -x c $(CFLAGS) -o testwssl.o - 2>/dev/null && $(CC) $(LDFLAGS) -o testwssl testwssl.o -lwolfssl 2>/dev/null && rm testwssl testwssl.o && echo true||echo false) WOLFSSL_CHECK = $(shell printf "\#include <wolfssl/options.h>\\n\#include <wolfssl/openssl/ssl.h>\\n int main(){return 0;}" | tr -d \\\\ | $(CC) -x c $(CFLAGS) -o testwssl.o - 2>/dev/null && $(CC) $(LDFLAGS) -o testwssl testwssl.o -lwolfssl 2>/dev/null && rm testwssl testwssl.o && echo true||echo false)
ifeq ($(WOLFSSL_CHECK), true) ifeq ($(WOLFSSL_CHECK), true)

View File

@ -18,7 +18,7 @@ SSL_LIBS = wolfssl.lib
SSL_DEFS = /D "WITH_SSL" SSL_DEFS = /D "WITH_SSL"
SSL_LIBS = libcrypto.lib libssl.lib SSL_LIBS = libcrypto.lib libssl.lib
!ENDIF !ENDIF
CFLAGS = /D "WITH_HTTPSRV" /nologo /MT /W3 /Ox /GS /EHs- /GA /GF /D "MSVC" /D "WITH_WSAPOLL" /D "NDEBUG" /D "WIN32" $(SSL_DEFS) /D "WITH_PCRE" /D "WITH_ODBC" /D "_CONSOLE" /D "_MBCS" /D "_WIN32" /D "NO_SHARE_UDP_SOCKET" /Fp"proxy.pch" /FD /c $(BUILDDATE) $(VERSION) CFLAGS = /D "WITH_HTTPSRV" /nologo /MT /W3 /Ox /GS /EHs- /GA /GF /D "MSVC" /D "WITH_WSAPOLL" /D "NDEBUG" /D "WIN32" $(SSL_DEFS) /D "WITH_PCRE" /D "WITH_ODBC" /D "_CONSOLE" /D "_MBCS" /D "_WIN32" /Fp"proxy.pch" /FD /c $(BUILDDATE) $(VERSION)
COUT = /Fo COUT = /Fo
LN = link LN = link
LDFLAGS = /nologo /subsystem:console /incremental:no LDFLAGS = /nologo /subsystem:console /incremental:no

View File

@ -46,13 +46,7 @@ AFTERCLEAN = (find . -type f -name "*.o" -delete && find src/ -type f -name "Mak
TYPECOMMAND = cat TYPECOMMAND = cat
COMPATLIBS = COMPATLIBS =
MAKEFILE = Makefile.unix MAKEFILE = Makefile.unix
PLUGINS ?= StringsPlugin TrafficPlugin FilePlugin PLUGINS ?= StringsPlugin TrafficPlugin TransparentPlugin FilePlugin
# Transparent proxying is not built here: this makefile is for the systems
# without a redirection 3proxy can read the original destination from. Where
# the platform has one - OpenBSD divert-to is the case that fits - uncomment:
#CFLAGS += -DWITH_TRANSPARENT
#TRANSPARENT_OBJS = transparent$(OBJSUFFICS)
ifeq ($(STATIC), true) ifeq ($(STATIC), true)
LDFLAGS += -static LDFLAGS += -static
CFLAGS += -DNOPLUGINS -DNOSTDRESOLVE -DNOCRYPT CFLAGS += -DNOPLUGINS -DNOSTDRESOLVE -DNOCRYPT

View File

@ -8,7 +8,7 @@ BUILDDIR = ../bin/
PREFIX = 3proxy_ PREFIX = 3proxy_
CRYPT_PREFIX = 3proxy_ CRYPT_PREFIX = 3proxy_
CC = cl CC = cl
CFLAGS = /D "WITH_HTTPSRV" /nologo /Ox /MT /D "NOIPV6" /D "NO_UN" /D "NODEBUG" /D "NORADIUS" /D"WATCOM" /D "NO_SHARE_UDP_SOCKET" /D "MSVC" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D "_WIN32" /D "PRId64=\"I64d\"" /D "PRIu64=\"I64u\"" /D "SCNu64=\"I64u\"" /D "SCNx64=\"I64x\"" /D "SCNd64=\"I64d\"" /D "PRIx64=\"I64x\"" /c $(VERSION) $(BUILDDATE) CFLAGS = /D "WITH_HTTPSRV" /nologo /Ox /MT /D "NOIPV6" /D "NO_UN" /D "NODEBUG" /D "NORADIUS" /D"WATCOM" /D "MSVC" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D "_WIN32" /D "PRId64=\"I64d\"" /D "PRIu64=\"I64u\"" /D "SCNu64=\"I64u\"" /D "SCNx64=\"I64x\"" /D "SCNd64=\"I64d\"" /D "PRIx64=\"I64x\"" /c $(VERSION) $(BUILDDATE)
COUT = /Fo COUT = /Fo
LN = link LN = link
LDFLAGS = /nologo /subsystem:console /incremental:no LDFLAGS = /nologo /subsystem:console /incremental:no

View File

@ -11,9 +11,6 @@ CRYPT_PREFIX ?= $(PREFIX)
CC ?= gcc CC ?= gcc
CFLAGS ?= -O3 -flto CFLAGS ?= -O3 -flto
CFLAGS += -fno-strict-aliasing -c -mthreads -DWITH_WSAPOLL -DWITH_ODBC CFLAGS += -fno-strict-aliasing -c -mthreads -DWITH_WSAPOLL -DWITH_ODBC
# Windows does not take a recv and a send on one socket from two threads,
# so a UDP service answers from a socket of its own
CFLAGS += -DNO_SHARE_UDP_SOCKET
COUT = -o COUT = -o
LN ?= $(CC) LN ?= $(CC)
LDFLAGS ?= -O3 -flto LDFLAGS ?= -O3 -flto

View File

@ -37,7 +37,6 @@
<li><a href="#SSLPLUGIN">How to set up TLS/SSL (https proxy, mTLS)</a></li> <li><a href="#SSLPLUGIN">How to set up TLS/SSL (https proxy, mTLS)</a></li>
<li><a href="#CERTIFICATES">How to create CA and certificates for SSL</a></li> <li><a href="#CERTIFICATES">How to create CA and certificates for SSL</a></li>
<li><a href="#PCRE">How to use PCRE filtering (regular expressions)</a></li> <li><a href="#PCRE">How to use PCRE filtering (regular expressions)</a></li>
<li><a href="#TRANSPARENT">How to proxy transparently</a></li>
<li><A HREF="#AUTH">How to limit service access</a> <li><A HREF="#AUTH">How to limit service access</a>
<li><A HREF="#USERS">How to create a user list</a> <li><A HREF="#USERS">How to create a user list</a>
<li><A HREF="#ACL">How to limit user access to resources</a> <li><A HREF="#ACL">How to limit user access to resources</a>
@ -982,189 +981,6 @@ openssl pkcs12 -export -out client.p12 -passout pass: \
openssl verify -x509_strict -CAfile ca.crt server.crt openssl verify -x509_strict -CAfile ca.crt server.crt
openssl verify -x509_strict -CAfile ca.crt client.crt openssl verify -x509_strict -CAfile ca.crt client.crt
</pre> </pre>
<li><a name="TRANSPARENT"><i>How to proxy transparently</i></a>
<p>
A transparent proxy serves clients that were never configured to use one. A
packet filter redirects their connections to 3proxy, and the
<b>transparent</b> command tells the service to take the destination from the
filter instead of from the request. Every other feature applies as usual:
access rules, parent proxies, limits and logging all see the real destination.
It works on Linux and on the BSDs. Since 1.0.1 it is part of the binary; before
that it was the separate TransparentPlugin, and the <b>plugin</b> line it
needed is no longer required.
</p>
<p>
The command supplies both the address and the port the client was trying to
reach, so a service can serve whatever was redirected to it rather than one
port with one target.
</p>
<p>
Without it a service has to get a destination from somewhere else: an HTTP
proxy falls back to the <b>Host</b> header, and a port mapper uses the address
it was configured with. Taking the destination from the filter is what makes
the other protocols work, and what makes the address authoritative rather than
something the client claimed.
</p>
<p>
<b>tlspr</b> is the clearest case. Nothing reaches it at all unless traffic is
redirected to it, or the clients resolve the names to it through DNS. With a
redirection it gets the address as well as the name from the handshake, and
that is what lets access rules be written with host names: the name from the
handshake is matched, and the connection still goes to the address the client
was going to. Without the address it would have to resolve the name itself,
which is a second lookup and a second answer.
</p>
<p>
A configuration for web and TLS traffic:
</p><pre>
log /var/log/3proxy.log D
auth iponly
allow *
&#35; the destination comes from the redirection for the services below
transparent
&#35; ordinary web traffic, redirected here from port 80
proxy -p3129 -e192.0.2.10
&#35; TLS, redirected here from port 443: tlspr would otherwise have only the
&#35; name in the handshake, and this gives it the address as well
tlspr -p3143 -e192.0.2.10
notransparent
</pre>
<p>
<b>-e</b> gives those services an address of their own to connect from. That
address is what the redirection rules exclude, and excluding it is what stops
the proxy's own connections from being redirected back into it. Without an
exclusion the connection 3proxy makes to the origin matches the same rule,
returns to 3proxy, and the traffic goes round until something gives out.
Running 3proxy as its own account and excluding that account works too, and is
the better choice when the machine has one address.
</p>
<p><b>Linux, iptables</b>. For traffic the machine forwards for others:
</p><pre>
iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 80 -j REDIRECT --to-ports 3129
iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 443 -j REDIRECT --to-ports 3143
</pre>
<p>
Traffic the machine generates itself passes through OUTPUT instead, where the
proxy's own connections have to be excluded:
</p><pre>
&#35; by the address the services connect from
iptables -t nat -A OUTPUT -p tcp --dport 80 ! -s 192.0.2.10 -j REDIRECT --to-ports 3129
&#35; or by the account 3proxy runs as
iptables -t nat -A OUTPUT -p tcp --dport 80 -m owner ! --uid-owner proxy3 \
-j REDIRECT --to-ports 3129
</pre>
<p><b>Linux, nftables</b> (Debian 11+, Ubuntu 22.04+, RHEL 8+, Fedora, Arch,
where nftables is what iptables is a front end for):
</p><pre>
table ip proxy3 {
chain prerouting {
type nat hook prerouting priority dstnat; policy accept;
iif "eth0" tcp dport 80 redirect to :3129
iif "eth0" tcp dport 443 redirect to :3143
}
chain output {
type nat hook output priority dstnat; policy accept;
meta skuid != "proxy3" tcp dport 80 redirect to :3129
meta skuid != "proxy3" tcp dport 443 redirect to :3143
}
}
</pre>
<p>
Load it with <b>nft -f</b>, and keep it across reboots in
<b>/etc/nftables.conf</b> (Debian, Ubuntu) or
<b>/etc/sysconfig/nftables.conf</b> (RHEL, Fedora). A table name cannot begin
with a digit, which is why the table above is not called 3proxy.
</p>
<p><b>Linux, firewalld</b> (RHEL, CentOS Stream, Fedora, openSUSE). Redirect an
incoming port on a zone:
</p><pre>
firewall-cmd --permanent --zone=internal --add-forward-port=port=80:proto=tcp:toport=3129
firewall-cmd --permanent --zone=internal --add-forward-port=port=443:proto=tcp:toport=3143
firewall-cmd --reload
</pre>
<p>
firewalld has no exclusion for the proxy's own traffic in that form, so put
that part in a direct rule:
</p><pre>
firewall-cmd --permanent --direct --add-rule ipv4 nat OUTPUT 0 \
-p tcp --dport 80 -m owner ! --uid-owner proxy3 -j REDIRECT --to-ports 3129
firewall-cmd --reload
</pre>
<p><b>Linux, ufw</b> (Ubuntu, Debian). ufw has no command for redirection;
add the rules to <b>/etc/ufw/before.rules</b>, above the <b>*filter</b> block:
</p><pre>
*nat
:PREROUTING ACCEPT [0:0]
-A PREROUTING -i eth0 -p tcp --dport 80 -j REDIRECT --to-ports 3129
-A PREROUTING -i eth0 -p tcp --dport 443 -j REDIRECT --to-ports 3143
COMMIT
</pre>
<p>
On Linux 3proxy asks the kernel where the connection was going. On the BSDs it
asks pf, which keeps the original destination in its state table, through
<b>/dev/pf</b> - so <b>rdr</b> rules work, and 3proxy has to be able to read
that device. Where a redirection leaves the destination on the socket instead,
that is used: OpenBSD <b>divert-to</b> and FreeBSD <b>ipfw fwd</b> both do.
</p>
<p>
The mechanism is chosen automatically, and <b>transparent</b> takes an
argument for the installations that need to pin it: <b>auto</b> (the default),
<b>netfilter</b>, <b>pf</b>, or <b>socket</b> for reading it off the socket. A
mode the build has no code for is refused, so a configuration written for
another platform fails where it is wrong instead of quietly doing something
else.
</p>
<p><b>FreeBSD, NetBSD and OpenBSD, pf</b>. Redirect in <b>/etc/pf.conf</b>,
excluding the address the proxy connects from:
</p><pre>
rdr pass on em0 inet proto tcp from ! 192.0.2.10 to any port 80 -&gt; 127.0.0.1 port 3129
rdr pass on em0 inet proto tcp from ! 192.0.2.10 to any port 443 -&gt; 127.0.0.1 port 3143
</pre>
<p>
Load with <b>pfctl -f /etc/pf.conf</b>. 3proxy looks the destination up in pf's
state table, so it needs to read <b>/dev/pf</b>: either run it as root, or give
its account access to that device.
</p>
<p><b>OpenBSD, divert-to</b> is an alternative which leaves the destination on
the socket, and needs no access to <b>/dev/pf</b>:
</p><pre>
pass in on em0 inet proto tcp to any port 80 divert-to 127.0.0.1 port 3129
pass in on em0 inet proto tcp to any port 443 divert-to 127.0.0.1 port 3143
</pre>
<p><b>FreeBSD, ipfw</b>. <b>fwd</b> delivers the connection locally without
rewriting it, which also leaves the destination on the socket:
</p><pre>
ipfw add fwd 127.0.0.1,3129 tcp from any to any 80 in recv em0
ipfw add fwd 127.0.0.1,3143 tcp from any to any 443 in recv em0
</pre>
<p><b>macOS</b> has <b>/dev/pf</b> but ships no header for it, so a macOS build
has no pf lookup, and macOS has neither <b>divert-to</b> nor ipfw to leave the
address on the socket. Transparent proxying is not usable there, even though
the commands exist in a macOS build.
</p>
<p>
Check the result by asking for a host through a redirected port and reading
the log: the request should appear with the address the client asked for,
which is what it would look like through a configured proxy.
</p>
<li><a name="PCRE"><i>How to use PCRE filtering (regular expressions)</i></a> <li><a name="PCRE"><i>How to use PCRE filtering (regular expressions)</i></a>
<p> <p>
Since version 0.9.7, PCRE (Perl Compatible Regular Expressions) filtering is built into Since version 0.9.7, PCRE (Perl Compatible Regular Expressions) filtering is built into

View File

@ -37,7 +37,6 @@
<li><a href="#SSLPLUGIN">Как настроить TLS/SSL (https прокси, mTLS)</a></li> <li><a href="#SSLPLUGIN">Как настроить TLS/SSL (https прокси, mTLS)</a></li>
<li><a href="#CERTIFICATES">Как создать CA и сертификаты для SSL</a></li> <li><a href="#CERTIFICATES">Как создать CA и сертификаты для SSL</a></li>
<li><a href="#PCRE">Как использовать PCRE-фильтрацию (регулярные выражения)</a></li> <li><a href="#PCRE">Как использовать PCRE-фильтрацию (регулярные выражения)</a></li>
<li><a href="#TRANSPARENT">Как сделать транспарентный прокси</a></li>
<li><a href="#AUTH">Как ограничить доступ к службе</a> <li><a href="#AUTH">Как ограничить доступ к службе</a>
<li><a href="#USERS">Как создать список пользователей</a> <li><a href="#USERS">Как создать список пользователей</a>
<li><a href="#ACL">Как ограничить доступ пользователей к ресурсам</a> <li><a href="#ACL">Как ограничить доступ пользователей к ресурсам</a>
@ -993,176 +992,6 @@ openssl verify -x509_strict -CAfile ca.crt server.crt
openssl verify -x509_strict -CAfile ca.crt client.crt openssl verify -x509_strict -CAfile ca.crt client.crt
</pre> </pre>
<li><a name="TRANSPARENT"><i>Как сделать транспарентный прокси</i></a>
<p>
Транспарентный прокси обслуживает клиентов, которые не настроены на работу
через прокси. Пакетный фильтр перенаправляет их соединения на 3proxy, а команда
<b>transparent</b> указывает сервису брать адрес назначения у фильтра, а не из
запроса. Всё остальное работает как обычно: правила доступа, родительские
прокси, ограничения и логирование видят настоящий адрес назначения. Работает в
Linux и BSD. С версии 1.0.1 встроено в бинарник, раньше это был отдельный
TransparentPlugin, и строка <b>plugin</b> больше не нужна.
</p>
<p>
Команда даёт и адрес, и порт назначения, поэтому сервис обслуживает всё, что
на него перенаправлено, а не один порт с одним адресом назначения.
</p>
<p>
Без неё сервис берёт адрес откуда-то ещё: HTTP-прокси - из заголовка
<b>Host</b>, порт-маппер - из своей конфигурации. Именно получение адреса от
фильтра позволяет работать с остальными протоколами и делает адрес
достоверным, а не заявленным клиентом.
</p>
<p>
Нагляднее всего это с <b>tlspr</b>. Без перенаправления трафика (или без
резолва имён на него через DNS) на него вообще ничего не попадёт. С
перенаправлением он получает и имя из TLS handshake, и адрес назначения -
именно это позволяет писать правила доступа по именам хостов: имя из handshake
проверяется в ACL, а соединение идёт на тот адрес, куда шёл клиент. Без адреса
пришлось бы резолвить имя самостоятельно, то есть делать ещё один запрос и
получать ещё один ответ.
</p>
<p>
Конфигурация для веб- и TLS-трафика:
</p><pre>
log /var/log/3proxy.log D
auth iponly
allow *
&#35; для сервисов ниже адрес назначения берётся из перенаправления
transparent
&#35; обычный веб-трафик, перенаправленный сюда с порта 80
proxy -p3129 -e192.0.2.10
&#35; TLS, перенаправленный сюда с порта 443: без этого у tlspr было бы только
&#35; имя из handshake, а так есть и адрес
tlspr -p3143 -e192.0.2.10
notransparent
</pre>
<p>
<b>-e</b> задаёт сервисам собственный адрес для исходящих соединений. Именно
этот адрес исключается в правилах перенаправления, и это исключение не даёт
соединениям самого прокси попасть обратно в него. Без исключения соединение,
которое 3proxy устанавливает к серверу назначения, попадает под то же правило,
возвращается в 3proxy, и трафик зацикливается. Можно вместо этого запускать
3proxy под отдельной учётной записью и исключать её - так лучше, если у машины
один адрес.
</p>
<p><b>Linux, iptables</b>. Для транзитного трафика:
</p><pre>
iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 80 -j REDIRECT --to-ports 3129
iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 443 -j REDIRECT --to-ports 3143
</pre>
<p>
Трафик самой машины проходит через цепочку OUTPUT, где соединения прокси нужно
исключить:
</p><pre>
&#35; по адресу, с которого сервисы устанавливают соединения
iptables -t nat -A OUTPUT -p tcp --dport 80 ! -s 192.0.2.10 -j REDIRECT --to-ports 3129
&#35; либо по учётной записи, под которой работает 3proxy
iptables -t nat -A OUTPUT -p tcp --dport 80 -m owner ! --uid-owner proxy3 \
-j REDIRECT --to-ports 3129
</pre>
<p><b>Linux, nftables</b> (Debian 11+, Ubuntu 22.04+, RHEL 8+, Fedora, Arch):
</p><pre>
table ip proxy3 {
chain prerouting {
type nat hook prerouting priority dstnat; policy accept;
iif "eth0" tcp dport 80 redirect to :3129
iif "eth0" tcp dport 443 redirect to :3143
}
chain output {
type nat hook output priority dstnat; policy accept;
meta skuid != "proxy3" tcp dport 80 redirect to :3129
meta skuid != "proxy3" tcp dport 443 redirect to :3143
}
}
</pre>
<p>
Загружается через <b>nft -f</b>, сохраняется в <b>/etc/nftables.conf</b>
(Debian, Ubuntu) или <b>/etc/sysconfig/nftables.conf</b> (RHEL, Fedora). Имя
таблицы не может начинаться с цифры, поэтому таблица называется не 3proxy.
</p>
<p><b>Linux, firewalld</b> (RHEL, CentOS Stream, Fedora, openSUSE):
</p><pre>
firewall-cmd --permanent --zone=internal --add-forward-port=port=80:proto=tcp:toport=3129
firewall-cmd --permanent --zone=internal --add-forward-port=port=443:proto=tcp:toport=3143
firewall-cmd --reload
</pre>
<p>
Исключение для трафика самого прокси в таком виде не задаётся, для него нужно
прямое правило:
</p><pre>
firewall-cmd --permanent --direct --add-rule ipv4 nat OUTPUT 0 \
-p tcp --dport 80 -m owner ! --uid-owner proxy3 -j REDIRECT --to-ports 3129
firewall-cmd --reload
</pre>
<p><b>Linux, ufw</b> (Ubuntu, Debian). В ufw нет команды для перенаправления,
правила добавляются в <b>/etc/ufw/before.rules</b> перед блоком <b>*filter</b>:
</p><pre>
*nat
:PREROUTING ACCEPT [0:0]
-A PREROUTING -i eth0 -p tcp --dport 80 -j REDIRECT --to-ports 3129
-A PREROUTING -i eth0 -p tcp --dport 443 -j REDIRECT --to-ports 3143
COMMIT
</pre>
<p>
В Linux 3proxy спрашивает у ядра, куда шло соединение. В BSD он спрашивает у pf,
который хранит исходный адрес назначения в таблице состояний, через
<b>/dev/pf</b> - поэтому работают правила <b>rdr</b>, и 3proxy должен иметь
доступ к этому устройству. Если перенаправление оставляет адрес на самом сокете,
используется он: так делают OpenBSD <b>divert-to</b> и FreeBSD <b>ipfw fwd</b>.
</p>
<p>
Механизм выбирается автоматически, а команда <b>transparent</b> принимает
аргумент для случаев, когда его надо зафиксировать: <b>auto</b> (по умолчанию),
<b>netfilter</b>, <b>pf</b> или <b>socket</b> для чтения адреса с сокета. Режим,
которого нет в сборке, отвергается, поэтому конфигурация, написанная для другой
платформы, не запустится вместо того, чтобы молча делать что-то другое.
</p>
<p><b>FreeBSD, NetBSD, OpenBSD, pf</b>. Перенаправление в <b>/etc/pf.conf</b> с
исключением адреса, с которого соединяется прокси:
</p><pre>
rdr pass on em0 inet proto tcp from ! 192.0.2.10 to any port 80 -&gt; 127.0.0.1 port 3129
rdr pass on em0 inet proto tcp from ! 192.0.2.10 to any port 443 -&gt; 127.0.0.1 port 3143
</pre>
<p>
Загружается через <b>pfctl -f /etc/pf.conf</b>. 3proxy ищет адрес назначения в
таблице состояний pf, поэтому ему нужен доступ на чтение к <b>/dev/pf</b>: либо
запуск от root, либо права на устройство для его учётной записи.
</p>
<p><b>OpenBSD, divert-to</b> - альтернатива, оставляющая адрес на сокете, доступ
к <b>/dev/pf</b> при этом не нужен:
</p><pre>
pass in on em0 inet proto tcp to any port 80 divert-to 127.0.0.1 port 3129
pass in on em0 inet proto tcp to any port 443 divert-to 127.0.0.1 port 3143
</pre>
<p><b>FreeBSD, ipfw</b>. <b>fwd</b> доставляет соединение локально, не переписывая
его, и адрес тоже остаётся на сокете:
</p><pre>
ipfw add fwd 127.0.0.1,3129 tcp from any to any 80 in recv em0
ipfw add fwd 127.0.0.1,3143 tcp from any to any 443 in recv em0
</pre>
<p><b>macOS</b>: <b>/dev/pf</b> есть, но заголовочных файлов для него нет,
поэтому в сборке под macOS нет обращения к pf, а ни <b>divert-to</b>, ни ipfw в
macOS нет. Транспарентное проксирование там неприменимо, хотя команды в сборке
присутствуют.
</p>
<li><a name="PCRE"><i>Как использовать PCRE-фильтрацию (регулярные выражения)</i></a> <li><a name="PCRE"><i>Как использовать PCRE-фильтрацию (регулярные выражения)</i></a>
<p> <p>
Начиная с версии 0.9.7 фильтрация PCRE встроена в 3proxy при компиляции с поддержкой Начиная с версии 0.9.7 фильтрация PCRE встроена в 3proxy при компиляции с поддержкой

View File

@ -1115,57 +1115,6 @@ users &quot;test4:CR:$3$salt$G47yV9w....&quot; <br>
Note: double quotes are required because the password Note: double quotes are required because the password
contains a $ sign.</p> contains a $ sign.</p>
<p style="margin-left:9%; margin-top: 1em"><b>transparent</b>
<i>[auto|netfilter|pf|socket]</i> <br>
Take the destination of a connection, both address and port,
from the packet filter that redirected it, instead of from
the request. It applies to services declared after it, and
<b>notransparent</b> turns it off again for the services
after that. Built into the binary since 1.0.1, and
previously the separate TransparentPlugin. <br>
On Linux the kernel is asked, so <b>iptables</b> or
<b>nftables</b> redirection is enough. On the BSDs pf is
asked through <b>/dev/pf</b>, which 3proxy must be able to
read, so <b>rdr</b> rules work; a redirection that leaves
the destination on the socket is used where there is one, as
<b>divert-to</b> on OpenBSD and <b>ipfw fwd</b> on FreeBSD
do. macOS ships no header for pf and has neither of those,
so the commands exist in a macOS build but cannot be used.
<br>
The mechanism is chosen automatically. The optional argument
pins it for an installation that has more than one:
<b>auto</b> is the default, <b>netfilter</b> asks the Linux
kernel, <b>pf</b> looks the connection up in the packet
filter, and <b>socket</b> reads the address off the socket.
A mode the build has no code for is refused rather than
ignored. <br>
A connection that reaches a <b>socket</b> mode service
without having been redirected is refused: its destination
is the address the service listens on, and using that would
send the service to itself. <br>
A redirected connection carries no destination of its own,
so without this the service uses whatever it would use
otherwise: the <b>Host</b> header for an HTTP request, or
the address a port mapper was configured with. <b>tlspr</b>
receives nothing at all unless traffic is redirected to it
or the clients resolve names to it, and with a redirection
it has the address as well as the name from the handshake,
which is what allows access rules to be written with host
names. With it, every service reaches the address the client
was trying to reach, and access rules, parents, limits and
logging apply to it as usual. <br>
The redirection rules must not match the connections the
proxy itself makes to those destinations, or the traffic
returns to the proxy and loops. Give the service an outgoing
address with <b>-e</b> and exclude that address in the
rules, or run 3proxy as its own user and exclude that user.
See the <b>TRANSPARENT PROXYING</b> section of the
documentation for rules per platform. <b><br>
notransparent</b> <br>
Stop taking the destination from the packet filter for the
services declared after it.</p>
<p style="margin-left:9%; margin-top: 1em"><b>flush</b> <p style="margin-left:9%; margin-top: 1em"><b>flush</b>
<br> <br>
empty the active access list. The access list must be empty the active access list. The access list must be

View File

@ -1,56 +1,31 @@
<h3>3proxy transparent proxying (Linux/BSD only)</h3> <h3>3proxy TransparentPlugin (Linux/BSD only)</h3>
Transparent proxying is part of 3proxy itself since 1.0.1. It was the separate This plugin can turn 3proxy into a transparent proxy for virtually any TCP-based protocol
TransparentPlugin before that, and the <b>plugin</b> line that used to load it and use all 3proxy features - redirections, parent proxies, ACLs, traffic limitations,
is no longer needed: the <b>transparent</b> and <b>notransparent</b> commands etc. The TransparentPlugin takes the destination IP:port from Linux and uses this
are always available on the platforms that can redirect a connection. information as the target IP in the proxy. An example usage:
<p>
It turns 3proxy into a transparent proxy for virtually any TCP-based protocol,
with the rest of 3proxy applying as usual - redirections, parent proxies, ACLs,
traffic limitations and logging. The destination IP and port come from the
packet filter that redirected the connection, and are used as the target of the
proxied connection.
</p>
<pre> <pre>
plugin /path/to/TransparentPlugin.ld.so transparent_plugin
log /path/to/log log /path/to/log
auth iponly auth iponly
allow * * * 80 allow * * * 80
parent 1000 http 0.0.0.0 0 parent 1000 http 0.0.0.0 0
allow * allow *
parent 1000 socks5 SOCKS5_IP SOCKS5_PORT USER PASSWORD parent 1000 socks5 SOCKS5_IP SOCKS5_PORT USER PASSWORD
transparent transparent
tcppm -eLOCAL_IP 12345 127.0.0.1 11111 tcppm -iLOCAL_IP 12345 127.0.0.1 11111
notransparent notransparent
proxy proxy
</pre> </pre>
Now, any TCP traffic transparently redirected to port 12345 will be routed via
the parent SOCKSv5 proxy and logged; all URLs for web requests are visible in logs.
The parameters '127.0.0.1 11111' in this case are not used and are overwritten by
the destination IP:port for each transparent connection.
<p> <h4>Download:</h4>
Any TCP traffic redirected to port 12345 is routed through the parent SOCKSv5 <ul>
proxy and logged, with the URLs of web requests visible in the log. The <li>Plugin is included in 3proxy 0.8
'127.0.0.1 11111' arguments are not used in that case: they are replaced by the </li></ul>
destination the client was trying to reach.
</p>
<p>
The destination is looked up in pf on the BSDs, through <b>/dev/pf</b>, and
asked of the kernel on Linux; a redirection that leaves the address on the
socket is used where there is one. <b>transparent</b> takes an optional
<b>auto</b>, <b>netfilter</b>, <b>pf</b> or <b>socket</b> to pin that choice.
</p>
<p>
The redirection rules must not match the connections 3proxy itself makes, or
the traffic returns to the proxy and loops. Give the service an address to
connect from with <b>-e</b> and exclude it in the rules, or run 3proxy as its
own account and exclude that account.
</p>
<p>
Redirection rules for iptables, nftables, firewalld, ufw and pf are in
<a href="../howtoe.html#TRANSPARENT">How to proxy transparently</a>, and the
commands are described in 3proxy.cfg(5).
</p>
&copy; Vladimir Dubrovin, License: BSD style &copy; Vladimir Dubrovin, License: BSD style

View File

@ -1,56 +1,33 @@
<h3>Транспарентное проксирование 3proxy (только для Linux/BSD)</h3> <h3>Плагин TransparentPlugin 3proxy (только для Linux/BSD)</h3>
Начиная с 1.0.1 транспарентное проксирование встроено в 3proxy. Раньше это был Плагин превращает 3proxy в транспарентный прокси для практически любых TCP-соединений
отдельный TransparentPlugin, и строка <b>plugin</b>, которой он загружался, и позволяет прозрачно для клиентов использовать весь фунционал прокси - редиректоры,
больше не нужна: команды <b>transparent</b> и <b>notransparent</b> доступны родительские прокси, ACLи, ограничения трафика. TransparentPlugin получает IP:port
всегда на тех платформах, где соединение можно перенаправить. назначения от Linux и использует эту информацию в качестве конечного адреса назначения.
<br>
<p> Пример использования:
3proxy становится транспарентным прокси практически для любых TCP-соединений,
причём весь остальной функционал работает как обычно - редиректоры,
родительские прокси, ACLи, ограничения трафика и логирование. IP и порт
назначения берутся у пакетного фильтра, перенаправившего соединение, и
используются как адрес назначения проксируемого соединения.
</p>
<pre> <pre>
plugin /path/to/TransparentPlugin.ld.so transparent_plugin
log /path/to/log log /path/to/log
auth iponly auth iponly
allow * * * 80 allow * * * 80
parent 1000 http 0.0.0.0 0 parent 1000 http 0.0.0.0 0
allow * allow *
parent 1000 socks5 SOCKS5_IP SOCKS5_PORT USER PASSWORD parent 1000 socks5 SOCKS5_IP SOCKS5_PORT USER PASSWORD
transparent transparent
tcppm -eLOCAL_IP 12345 127.0.0.1 11111 tcppm -iLOCAL_IP 12345 127.0.0.1 11111
notransparent notransparent
proxy proxy
</pre> </pre>
Теперь любые TCP-соединения транспарентно перенаправленные в локальный порт 12345
<p> будут прологгированы и перенаправлены в родительский SOCKSv5 proxy, при этом для
Любой TCP-трафик, перенаправленный на порт 12345, пойдёт через родительский HTTP-запросов по порту TCP/80 будут видны параметры HTTP-запроса.
SOCKSv5 прокси и будет залогирован, URL веб-запросов видны в логе. Аргументы Параметры '127.0.0.1 11111' в данном случае не оказывают влияния, т.к.
'127.0.0.1 11111' в этом случае не используются: они заменяются адресом, к будут перезаписываться IP и портом назначения для каждого TCP-соединения соответственно.
которому обращался клиент. <h4>Загрузить:</h4>
</p> <ul>
<li>Плагин включен в дистрибутив 3proxy 0.8
<p> </li></ul>
В BSD адрес назначения ищется в pf через <b>/dev/pf</b>, в Linux запрашивается у
ядра; если перенаправление оставляет адрес на сокете, используется он. Команда
<b>transparent</b> принимает необязательный аргумент <b>auto</b>,
<b>netfilter</b>, <b>pf</b> или <b>socket</b>, чтобы зафиксировать выбор.
</p>
<p>
Правила перенаправления не должны попадать на соединения, которые устанавливает
сам 3proxy, иначе трафик возвращается в прокси и зацикливается. Задайте сервису
адрес для исходящих соединений через <b>-e</b> и исключите его в правилах, либо
запускайте 3proxy под отдельной учётной записью и исключайте её.
</p>
<p>
Правила перенаправления для iptables, nftables, firewalld, ufw и pf приведены в
<a href="../howtor.html#TRANSPARENT">описании транспарентного проксирования</a>,
команды описаны в 3proxy.cfg(5).
</p>
&copy; Vladimir Dubrovin, License: BSD style &copy; Vladimir Dubrovin, License: BSD style

View File

@ -1192,54 +1192,6 @@ the format:
Note: double quotes are required because the password contains a $ sign. Note: double quotes are required because the password contains a $ sign.
.br .br
.BR transparent
\fI[auto|netfilter|pf|socket]\fR
.br
Take the destination of a connection, both address and port, from the packet
filter that redirected it, instead of from the request. It applies to services declared after it, and
\fBnotransparent\fR turns it off again for the services after that. Built into the
binary since 1.0.1, and previously the separate TransparentPlugin.
.br
On Linux the kernel is asked, so \fBiptables\fR or \fBnftables\fR
redirection is enough. On the BSDs pf is asked through \fB/dev/pf\fR, which
3proxy must be able to read, so \fBrdr\fR rules work; a redirection that
leaves the destination on the socket is used where there is one, as
\fBdivert-to\fR on OpenBSD and \fBipfw fwd\fR on FreeBSD do. macOS ships no
header for pf and has neither of those, so the commands exist in a macOS build
but cannot be used.
.br
The mechanism is chosen automatically. The optional argument pins it for an
installation that has more than one: \fBauto\fR is the default,
\fBnetfilter\fR asks the Linux kernel, \fBpf\fR looks the connection up in
the packet filter, and \fBsocket\fR reads the address off the socket. A mode
the build has no code for is refused rather than ignored.
.br
A connection that reaches a \fBsocket\fR mode service without having been
redirected is refused: its destination is the address the service listens on,
and using that would send the service to itself.
.br
A redirected connection carries no destination of its own, so without this the
service uses whatever it would use otherwise: the \fBHost\fR header for an
HTTP request, or the address a port mapper was configured with. \fBtlspr\fR
receives nothing at all unless traffic is redirected to it or the clients
resolve names to it, and with a redirection it has the address as well as the
name from the handshake, which is what allows access rules to be written with
host names. With it, every service reaches the address the
client was trying to reach, and access rules, parents, limits and logging apply
to it as usual.
.br
The redirection rules must not match the connections the proxy itself makes to
those destinations, or the traffic returns to the proxy and loops. Give the
service an outgoing address with \fB-e\fR and exclude that address in the rules,
or run 3proxy as its own user and exclude that user. See the
.B TRANSPARENT PROXYING
section of the documentation for rules per platform.
.br
.BR notransparent
.br
Stop taking the destination from the packet filter for the services declared
after it.
.B flush .B flush
.br .br
empty the active access list. The access list must be flushed every time you create a empty the active access list. The access list must be flushed every time you create a

View File

@ -13,9 +13,6 @@ void ssl_install(void);
#ifdef WITH_PCRE #ifdef WITH_PCRE
void pcre_install(void); void pcre_install(void);
#endif #endif
#ifdef WITH_TRANSPARENT
void transparent_install(void);
#endif
#ifndef _WIN32 #ifndef _WIN32
#include <sys/resource.h> #include <sys/resource.h>
#ifndef NOPLUGINS #ifndef NOPLUGINS
@ -532,9 +529,6 @@ int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int
#ifdef WITH_PCRE #ifdef WITH_PCRE
pcre_install(); pcre_install();
#endif #endif
#ifdef WITH_TRANSPARENT
transparent_install();
#endif
freeconf(&conf); freeconf(&conf);
initcommands(); initcommands();

View File

@ -116,9 +116,6 @@ srvsocks$(OBJSUFFICS): socks.c proxy.h structures.h
srvwebadmin$(OBJSUFFICS): webadmin.c proxy.h structures.h srvwebadmin$(OBJSUFFICS): webadmin.c proxy.h structures.h
$(CC) $(COUT)srvwebadmin$(OBJSUFFICS) $(CFLAGS) webadmin.c $(CC) $(COUT)srvwebadmin$(OBJSUFFICS) $(CFLAGS) webadmin.c
transparent$(OBJSUFFICS): transparent.c proxy.h structures.h
$(CC) $(COUT)transparent$(OBJSUFFICS) $(CFLAGS) transparent.c
srvhttpsrv$(OBJSUFFICS): httpsrv.c proxy.h structures.h srvhttpsrv$(OBJSUFFICS): httpsrv.c proxy.h structures.h
$(CC) $(COUT)srvhttpsrv$(OBJSUFFICS) $(CFLAGS) httpsrv.c $(CC) $(COUT)srvhttpsrv$(OBJSUFFICS) $(CFLAGS) httpsrv.c
@ -194,6 +191,6 @@ ssl$(OBJSUFFICS): ssl.c structures.h proxy.h ssl.h
pcre$(OBJSUFFICS): pcre.c structures.h pcre$(OBJSUFFICS): pcre.c structures.h
$(CC) $(COUT)pcre$(OBJSUFFICS) $(CFLAGS) $(DEFINEOPTION)WITH_PCRE pcre.c $(CC) $(COUT)pcre$(OBJSUFFICS) $(CFLAGS) $(DEFINEOPTION)WITH_PCRE pcre.c
$(BUILDDIR)3proxy$(EXESUFFICS): 3proxy$(OBJSUFFICS) mainfunc$(OBJSUFFICS) srvproxy$(OBJSUFFICS) srvpop3p$(OBJSUFFICS) srvimapp$(OBJSUFFICS) srvsmtpp$(OBJSUFFICS) srvftppr$(OBJSUFFICS) srvsocks$(OBJSUFFICS) srvtcppm$(OBJSUFFICS) srvtlspr$(OBJSUFFICS) srvauto$(OBJSUFFICS) srvudppm$(OBJSUFFICS) sockmap$(OBJSUFFICS) udpsockmap$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) common$(OBJSUFFICS) auth$(OBJSUFFICS) acl$(OBJSUFFICS) limiter$(OBJSUFFICS) redirect$(OBJSUFFICS) authradius$(OBJSUFFICS) hash$(OBJSUFFICS) hashtables$(OBJSUFFICS) resolve$(OBJSUFFICS) sql$(OBJSUFFICS) conf$(OBJSUFFICS) log$(OBJSUFFICS) datatypes$(OBJSUFFICS) md4$(OBJSUFFICS) md5$(OBJSUFFICS) blake2$(OBJSUFFICS) 3proxy_crypt$(OBJSUFFICS) base64$(OBJSUFFICS) ftp$(OBJSUFFICS) stringtable$(OBJSUFFICS) $(HTTPSRV_OBJS) srvdnspr$(OBJSUFFICS) plugins$(OBJSUFFICS) mdhash$(OBJSUFFICS) $(SSL_OBJS) $(PCRE_OBJS) $(TRANSPARENT_OBJS) $(COMPATLIBS) $(VERSIONDEP) $(BUILDDIR)3proxy$(EXESUFFICS): 3proxy$(OBJSUFFICS) mainfunc$(OBJSUFFICS) srvproxy$(OBJSUFFICS) srvpop3p$(OBJSUFFICS) srvimapp$(OBJSUFFICS) srvsmtpp$(OBJSUFFICS) srvftppr$(OBJSUFFICS) srvsocks$(OBJSUFFICS) srvtcppm$(OBJSUFFICS) srvtlspr$(OBJSUFFICS) srvauto$(OBJSUFFICS) srvudppm$(OBJSUFFICS) sockmap$(OBJSUFFICS) udpsockmap$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) common$(OBJSUFFICS) auth$(OBJSUFFICS) acl$(OBJSUFFICS) limiter$(OBJSUFFICS) redirect$(OBJSUFFICS) authradius$(OBJSUFFICS) hash$(OBJSUFFICS) hashtables$(OBJSUFFICS) resolve$(OBJSUFFICS) sql$(OBJSUFFICS) conf$(OBJSUFFICS) log$(OBJSUFFICS) datatypes$(OBJSUFFICS) md4$(OBJSUFFICS) md5$(OBJSUFFICS) blake2$(OBJSUFFICS) 3proxy_crypt$(OBJSUFFICS) base64$(OBJSUFFICS) ftp$(OBJSUFFICS) stringtable$(OBJSUFFICS) $(HTTPSRV_OBJS) srvdnspr$(OBJSUFFICS) plugins$(OBJSUFFICS) mdhash$(OBJSUFFICS) $(SSL_OBJS) $(PCRE_OBJS) $(COMPATLIBS) $(VERSIONDEP)
$(LN) $(LNOUT)$(BUILDDIR)3proxy$(EXESUFFICS) $(LDFLAGS) $(VERFILE) 3proxy$(OBJSUFFICS) mainfunc$(OBJSUFFICS) auth$(OBJSUFFICS) acl$(OBJSUFFICS) limiter$(OBJSUFFICS) redirect$(OBJSUFFICS) authradius$(OBJSUFFICS) hash$(OBJSUFFICS) hashtables$(OBJSUFFICS) resolve$(OBJSUFFICS) sql$(OBJSUFFICS) conf$(OBJSUFFICS) datatypes$(OBJSUFFICS) srvauto$(OBJSUFFICS) srvproxy$(OBJSUFFICS) srvpop3p$(OBJSUFFICS) srvimapp$(OBJSUFFICS) srvsmtpp$(OBJSUFFICS) srvftppr$(OBJSUFFICS) srvsocks$(OBJSUFFICS) srvtcppm$(OBJSUFFICS) srvtlspr$(OBJSUFFICS) srvudppm$(OBJSUFFICS) sockmap$(OBJSUFFICS) udpsockmap$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) common$(OBJSUFFICS) log$(OBJSUFFICS) 3proxy_crypt$(OBJSUFFICS) md4$(OBJSUFFICS) md5$(OBJSUFFICS) blake2$(OBJSUFFICS) base64$(OBJSUFFICS) ftp$(OBJSUFFICS) stringtable$(OBJSUFFICS) $(HTTPSRV_OBJS) srvdnspr$(OBJSUFFICS) plugins$(OBJSUFFICS) mdhash$(OBJSUFFICS) $(SSL_OBJS) $(PCRE_OBJS) $(TRANSPARENT_OBJS) $(COMPATLIBS) $(LIBS) $(PCRE_LIBS) $(LN) $(LNOUT)$(BUILDDIR)3proxy$(EXESUFFICS) $(LDFLAGS) $(VERFILE) 3proxy$(OBJSUFFICS) mainfunc$(OBJSUFFICS) auth$(OBJSUFFICS) acl$(OBJSUFFICS) limiter$(OBJSUFFICS) redirect$(OBJSUFFICS) authradius$(OBJSUFFICS) hash$(OBJSUFFICS) hashtables$(OBJSUFFICS) resolve$(OBJSUFFICS) sql$(OBJSUFFICS) conf$(OBJSUFFICS) datatypes$(OBJSUFFICS) srvauto$(OBJSUFFICS) srvproxy$(OBJSUFFICS) srvpop3p$(OBJSUFFICS) srvimapp$(OBJSUFFICS) srvsmtpp$(OBJSUFFICS) srvftppr$(OBJSUFFICS) srvsocks$(OBJSUFFICS) srvtcppm$(OBJSUFFICS) srvtlspr$(OBJSUFFICS) srvudppm$(OBJSUFFICS) sockmap$(OBJSUFFICS) udpsockmap$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) common$(OBJSUFFICS) log$(OBJSUFFICS) 3proxy_crypt$(OBJSUFFICS) md4$(OBJSUFFICS) md5$(OBJSUFFICS) blake2$(OBJSUFFICS) base64$(OBJSUFFICS) ftp$(OBJSUFFICS) stringtable$(OBJSUFFICS) $(HTTPSRV_OBJS) srvdnspr$(OBJSUFFICS) plugins$(OBJSUFFICS) mdhash$(OBJSUFFICS) $(SSL_OBJS) $(PCRE_OBJS) $(COMPATLIBS) $(LIBS) $(PCRE_LIBS)

View File

@ -18,9 +18,6 @@ void ssl_install(void);
#ifdef WITH_PCRE #ifdef WITH_PCRE
void pcre_install(void); void pcre_install(void);
#endif #endif
#ifdef WITH_TRANSPARENT
void transparent_install(void);
#endif
#ifndef _WIN32 #ifndef _WIN32
#include <sys/resource.h> #include <sys/resource.h>
#include <pwd.h> #include <pwd.h>
@ -1624,11 +1621,6 @@ static int h_plugin(int argc, unsigned char **argv){
return 0; return 0;
} }
#endif #endif
#ifdef WITH_TRANSPARENT
if(argc >= 3 && !strcmp((char *)argv[2], "transparent_plugin")){
return 0;
}
#endif
#ifdef NOPLUGINS #ifdef NOPLUGINS
return 999; return 999;
#else #else
@ -1801,10 +1793,6 @@ int h_server_verify(int argc, unsigned char **argv);
int h_no_server_verify(int argc, unsigned char **argv); int h_no_server_verify(int argc, unsigned char **argv);
int h_client_mode(int argc, unsigned char **argv); int h_client_mode(int argc, unsigned char **argv);
#endif #endif
#ifdef WITH_TRANSPARENT
int h_transparent(int argc, unsigned char **argv);
int h_notransparent(int argc, unsigned char **argv);
#endif
#ifdef WITH_PCRE #ifdef WITH_PCRE
int h_pcre(int argc, unsigned char **argv); int h_pcre(int argc, unsigned char **argv);
int h_pcre_rewrite(int argc, unsigned char **argv); int h_pcre_rewrite(int argc, unsigned char **argv);
@ -1934,10 +1922,6 @@ struct commands commandhandlers[]={
{NULL, "ssl_client_mode", h_client_mode, 1, 2}, {NULL, "ssl_client_mode", h_client_mode, 1, 2},
{NULL, "ssl_certcache", h_certcache, 2, 2}, {NULL, "ssl_certcache", h_certcache, 2, 2},
#endif #endif
#ifdef WITH_TRANSPARENT
{NULL, "transparent", h_transparent, 1, 2},
{NULL, "notransparent", h_notransparent, 1, 1},
#endif
#ifdef WITH_PCRE #ifdef WITH_PCRE
{NULL, "pcre", h_pcre, 4, 0}, {NULL, "pcre", h_pcre, 4, 0},
{NULL, "pcre_rewrite", h_pcre_rewrite, 5, 0}, {NULL, "pcre_rewrite", h_pcre_rewrite, 5, 0},
@ -2228,9 +2212,6 @@ int reload (void){
#endif #endif
#ifdef WITH_PCRE #ifdef WITH_PCRE
pcre_install(); pcre_install();
#endif
#ifdef WITH_TRANSPARENT
transparent_install();
#endif #endif
conf.paused++; conf.paused++;
freeconf(&conf); freeconf(&conf);

View File

@ -45,7 +45,7 @@ void * dnsprchild(struct clientparam* param) {
memcpy(buf, param->srv->udpbuf, i); memcpy(buf, param->srv->udpbuf, i);
_3proxy_sem_unlock(udpinit); _3proxy_sem_unlock(udpinit);
semlocked = 0; semlocked = 0;
#if defined(_WIN32) && !defined(SHARE_UDP_SOCKET) #ifdef _WIN32
if((param->clisock=param->srv->so._socket(param->sostate, AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == INVALID_SOCKET) { if((param->clisock=param->srv->so._socket(param->sostate, AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == INVALID_SOCKET) {
RETURN(818); RETURN(818);
} }
@ -56,8 +56,6 @@ void * dnsprchild(struct clientparam* param) {
} }
#else #else
/* The reply has to come from the address the query was sent to, which
is the listening socket. */
param->clisock = param->srv->srvsock; param->clisock = param->srv->srvsock;
#endif #endif
@ -219,8 +217,7 @@ CLEANRET:
} }
if(bbuf)free(bbuf); if(bbuf)free(bbuf);
if(host)free(host); if(host)free(host);
#if !defined(_WIN32) || defined(SHARE_UDP_SOCKET) #ifndef _WIN32
/* The socket belongs to the service, so the caller must not close it. */
param->clisock = INVALID_SOCKET; param->clisock = INVALID_SOCKET;
#endif #endif
return (NULL); return (NULL);

View File

@ -0,0 +1,6 @@
# TransparentPlugin
# Works on Linux (with netfilter), BSD and macOS (without netfilter support)
add_3proxy_plugin(TransparentPlugin
SOURCES transparent_plugin.c
)

View File

@ -0,0 +1 @@
include Makefile.var

View File

@ -0,0 +1,10 @@
all: $(BUILDDIR)TransparentPlugin$(DLSUFFICS)
transparent_plugin$(OBJSUFFICS): transparent_plugin.c
$(CC) $(CFLAGS) $(DCFLAGS) transparent_plugin.c
$(BUILDDIR)TransparentPlugin$(DLSUFFICS): transparent_plugin$(OBJSUFFICS)
$(LN) $(LNOUT)../../$(BUILDDIR)TransparentPlugin$(DLSUFFICS) $(LDFLAGS) $(DLFLAGS) transparent_plugin$(OBJSUFFICS)

View File

@ -0,0 +1,128 @@
/*
3APA3A simplest proxy server
(c) 2002-2026 by Vladimir Dubrovin <vlad@3proxy.org>
please read License Agreement
*/
#ifdef WITH_NETFILTER
#include <sys/utsname.h>
#endif
#include "../../structures.h"
#include "../../proxy.h"
#ifdef WITH_NETFILTER
#include <sys/types.h>
#include <sys/socket.h>
#include <limits.h>
#include <linux/netfilter_ipv4.h>
#endif
#ifdef __cplusplus
extern "C" {
#endif
static struct pluginlink * pl;
static int transparent_loaded = 0;
static void* transparent_filter_open(void * idata, struct srvparam * param){
return idata;
}
static FILTER_ACTION transparent_filter_client(void *fo, struct clientparam * param, void** fc){
char addrbuf[64];
#ifdef WITH_NETFILTER
socklen_t len;
len = sizeof(param->req);
#ifdef SO_ORIGINAL_DST
if(getsockopt(param->clisock,
#ifndef NOIPV6
#ifdef SOL_IPV6
*SAFAMILY(&param->sincr) == AF_INET6?SOL_IPV6:
#endif
#endif
SOL_IP, SO_ORIGINAL_DST,(struct sockaddr *) &param->req, &len) || !memcmp((char *)SAADDR(&param->req), "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", SAADDRLEN(&param->req))){
return PASS;
}
#else
#error No SO_ORIGINAL_DST defined
param->srv->logfunc(param, (unsigned char *)"transparent_plugin: No SO_ORIGINAL_DST defined");
return REJECT;
#endif
#else
if(*SAFAMILY(&param->sincl) == AF_INET || *SAFAMILY(&param->sincl) == AF_INET6){
param->req = param->sincl;
param->sincl = param->srv->intsa;
}
#endif
pl->myinet_ntop(*SAFAMILY(&param->req), SAADDR(&param->req), (char *)addrbuf, sizeof(addrbuf));
if(param->hostname) pl->freefunc(param->hostname);
param->hostname = (unsigned char *)pl->strdupfunc(addrbuf);
param->sinsr = param->req;
return PASS;
}
static void transparent_filter_clear(void *fo){
}
static void transparent_filter_close(void *fo){
}
static struct filter transparent_filter = {
NULL,
"Transparent filter",
"Transparent filter",
transparent_filter_open,
transparent_filter_client,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
transparent_filter_clear,
transparent_filter_close
};
static int h_transparent(int argc, unsigned char **argv){
transparent_filter.filter_open = transparent_filter_open;
return 0;
}
static int h_notransparent(int argc, unsigned char **argv){
transparent_filter.filter_open = NULL;
return 0;
}
static struct commands transparent_commandhandlers[] = {
{transparent_commandhandlers+1, "transparent", h_transparent, 1, 1},
{NULL, "notransparent", h_notransparent, 1, 1}
};
#ifdef WATCOM
#pragma aux transparent_plugin "*" parm caller [ ] value struct float struct routine [eax] modify [eax ecx edx]
#undef PLUGINCALL
#define PLUGINCALL
#endif
PLUGINAPI int PLUGINCALL transparent_plugin (struct pluginlink * pluginlink,
int argc, char** argv){
pl = pluginlink;
if(!transparent_loaded){
transparent_loaded = 1;
transparent_filter.next = pl->conf->filters;
pl->conf->filters = &transparent_filter;
transparent_commandhandlers[1].next = pl->commandhandlers->next;
pl->commandhandlers->next = transparent_commandhandlers;
}
return 0;
}
#ifdef __cplusplus
}
#endif

View File

@ -152,21 +152,6 @@ void daemonize(void);
#endif #endif
#endif #endif
/* A UDP service answers from the address the client sent to, so the reply has
to leave the listening socket. Sharing that socket with the request handler
is the simple way, and what every Unix build does.
Older Windows cannot have two operations in flight on one socket, so a
build for it binds a second socket to the same address instead, which needs
SO_REUSEADDR on the listening socket as well. A build for those versions
asks for that with NO_SHARE_UDP_SOCKET, as Makefile.watcom does.
*/
#ifndef SHARE_UDP_SOCKET
#ifndef NO_SHARE_UDP_SOCKET
#define SHARE_UDP_SOCKET
#endif
#endif
#ifndef _WIN32 #ifndef _WIN32
size_t threadstacksize(int extra); size_t threadstacksize(int extra);
#endif #endif

View File

@ -835,21 +835,10 @@ int MODULEMAINFUNC (int argc, char** argv){
port there, and it only allows another local process to bind the same port there, and it only allows another local process to bind the same
address and port, with undefined behaviour as to which socket receives the address and port, with undefined behaviour as to which socket receives the
connections. Use -olSO_EXCLUSIVEADDRUSE to prevent that instead. connections. Use -olSO_EXCLUSIVEADDRUSE to prevent that instead.
A Windows build which does not share the listening socket is the exception:
its UDP services bind a second socket to the same address to answer from,
and Windows only allows that when both sockets ask for it.
*/ */
#ifndef _WIN32 #ifndef _WIN32
opt = 1; opt = 1;
if(srv.so._setsockopt(srv.so.state, sock, SOL_SOCKET, SO_REUSEADDR, (char *)&opt, sizeof(int)))perror("setsockopt()"); if(srv.so._setsockopt(srv.so.state, sock, SOL_SOCKET, SO_REUSEADDR, (char *)&opt, sizeof(int)))perror("setsockopt()");
#else
#ifndef SHARE_UDP_SOCKET
if(isudp){
opt = 1;
if(srv.so._setsockopt(srv.so.state, sock, SOL_SOCKET, SO_REUSEADDR, (char *)&opt, sizeof(int)))perror("setsockopt()");
}
#endif
#endif #endif
#ifdef SO_REUSEPORT #ifdef SO_REUSEPORT
opt = 1; opt = 1;

View File

@ -1,240 +0,0 @@
/*
3APA3A simplest proxy server
(c) 2002-2026 by Vladimir Dubrovin <vlad@3proxy.org>
please read License Agreement
*/
#include "structures.h"
#include "proxy.h"
#ifdef WITH_TRANSPARENT
#ifdef WITH_NETFILTER
#include <sys/utsname.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <limits.h>
#include <linux/netfilter_ipv4.h>
#endif
#ifdef WITH_PF
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <net/if.h>
#include <net/pfvar.h>
#endif
/* Where the address the client was trying to reach is read from.
AUTO uses what the platform offers, which is the only thing an
installation usually needs. The rest name one mechanism, for a machine
that has more than one and redirects with a particular one.
*/
#define TRANSPARENT_AUTO 0
#define TRANSPARENT_NETFILTER 1
#define TRANSPARENT_PF 2
#define TRANSPARENT_SOCKET 3
static struct pluginlink * pl;
static int transparent_loaded = 0;
static int transparent_mode = TRANSPARENT_AUTO;
#ifdef WITH_PF
static int pf_device = -1;
/* Ask the packet filter what the connection was addressed to before it was
redirected. pf keeps that in its state table rather than on the socket,
so it has to be looked up with the addresses of both ends.
*/
static int transparent_pf(struct clientparam *param)
{
struct pfioc_natlook nl;
if(pf_device < 0){
pf_device = open("/dev/pf", O_RDONLY);
if(pf_device < 0) return 1;
}
memset(&nl, 0, sizeof(nl));
nl.proto = IPPROTO_TCP;
nl.direction = PF_OUT;
#ifndef NOIPV6
if(*SAFAMILY(&param->sincr) == AF_INET6){
nl.af = AF_INET6;
memcpy(&nl.saddr.v6, SAADDR(&param->sincr), 16);
memcpy(&nl.daddr.v6, SAADDR(&param->sincl), 16);
}
else
#endif
{
nl.af = AF_INET;
memcpy(&nl.saddr.v4, SAADDR(&param->sincr), 4);
memcpy(&nl.daddr.v4, SAADDR(&param->sincl), 4);
}
nl.sport = *SAPORT(&param->sincr);
nl.dport = *SAPORT(&param->sincl);
if(ioctl(pf_device, DIOCNATLOOK, &nl)) return 1;
memset(&param->req, 0, sizeof(param->req));
*SAFAMILY(&param->req) = nl.af;
#ifndef NOIPV6
if(nl.af == AF_INET6) memcpy(SAADDR(&param->req), &nl.rdaddr.v6, 16);
else
#endif
memcpy(SAADDR(&param->req), &nl.rdaddr.v4, 4);
*SAPORT(&param->req) = nl.rdport;
return 0;
}
#endif
#ifdef WITH_NETFILTER
/* Linux keeps the original address for the connection it redirected. */
static int transparent_netfilter(struct clientparam *param)
{
socklen_t len = sizeof(param->req);
#ifdef SO_ORIGINAL_DST
if(getsockopt(param->clisock,
#ifndef NOIPV6
#ifdef SOL_IPV6
*SAFAMILY(&param->sincr) == AF_INET6?SOL_IPV6:
#endif
#endif
SOL_IP, SO_ORIGINAL_DST, (struct sockaddr *) &param->req, &len)
|| !memcmp((char *)SAADDR(&param->req), "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", SAADDRLEN(&param->req))){
return 1;
}
return 0;
#else
#error No SO_ORIGINAL_DST defined
#endif
}
#endif
/* Some redirections leave the original address on the socket itself, so the
local address of the accepted connection is what the client asked for.
A connection which was not redirected at all arrives at the address the
service listens on, and taking that as the destination would send the
service to itself. Refuse instead of making the connection.
*/
static int transparent_socket(struct clientparam *param)
{
if(*SAFAMILY(&param->sincl) != AF_INET && *SAFAMILY(&param->sincl) != AF_INET6)
return 1;
if(*SAPORT(&param->sincl) == *SAPORT(&param->srv->intsa)
&& (SAISNULL(&param->srv->intsa)
|| !memcmp(SAADDR(&param->sincl), SAADDR(&param->srv->intsa), SAADDRLEN(&param->sincl))))
return 2;
param->req = param->sincl;
param->sincl = param->srv->intsa;
return 0;
}
static void* transparent_filter_open(void * idata, struct srvparam * param){
return idata;
}
static FILTER_ACTION transparent_filter_client(void *fo, struct clientparam * param, void** fc){
char addrbuf[64];
int res = 1;
#ifdef WITH_NETFILTER
if(transparent_mode == TRANSPARENT_AUTO || transparent_mode == TRANSPARENT_NETFILTER)
res = transparent_netfilter(param);
#endif
#ifdef WITH_PF
if(res && (transparent_mode == TRANSPARENT_AUTO || transparent_mode == TRANSPARENT_PF))
res = transparent_pf(param);
#endif
if(res && (transparent_mode == TRANSPARENT_AUTO || transparent_mode == TRANSPARENT_SOCKET)){
res = transparent_socket(param);
if(res == 2){
param->srv->logfunc(param, (unsigned char *)"transparent: connection was not redirected");
return REJECT;
}
}
/* Nothing knows where this was going: leave the request alone, so the
service decides as it would without the command. */
if(res) return PASS;
pl->myinet_ntop(*SAFAMILY(&param->req), SAADDR(&param->req), (char *)addrbuf, sizeof(addrbuf));
if(param->hostname) pl->freefunc(param->hostname);
param->hostname = (unsigned char *)pl->strdupfunc(addrbuf);
param->sinsr = param->req;
return PASS;
}
static void transparent_filter_clear(void *fo){
}
static void transparent_filter_close(void *fo){
}
static struct filter transparent_filter = {
NULL,
"Transparent filter",
"Transparent filter",
transparent_filter_open,
transparent_filter_client,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
transparent_filter_clear,
transparent_filter_close
};
int h_transparent(int argc, unsigned char **argv){
transparent_mode = TRANSPARENT_AUTO;
if(argc > 1){
if(!strcmp((char *)argv[1], "auto")) transparent_mode = TRANSPARENT_AUTO;
else if(!strcmp((char *)argv[1], "netfilter")){
#ifndef WITH_NETFILTER
fprintf(stderr, "transparent: netfilter is not available in this build\n");
return 1;
#else
transparent_mode = TRANSPARENT_NETFILTER;
#endif
}
else if(!strcmp((char *)argv[1], "pf")){
#ifndef WITH_PF
fprintf(stderr, "transparent: pf is not available in this build\n");
return 1;
#else
transparent_mode = TRANSPARENT_PF;
#endif
}
else if(!strcmp((char *)argv[1], "socket")) transparent_mode = TRANSPARENT_SOCKET;
else {
fprintf(stderr, "transparent: unknown mode %s, expected auto, netfilter, pf or socket\n", argv[1]);
return 1;
}
}
transparent_filter.filter_open = transparent_filter_open;
return 0;
}
int h_notransparent(int argc, unsigned char **argv){
transparent_filter.filter_open = NULL;
return 0;
}
void transparent_install(void){
pl = &pluginlink;
/* A reload runs this again: the filter is a single static entry, so it
is only linked in once, and the commands decide whether it acts. */
if(!transparent_loaded){
transparent_loaded = 1;
transparent_filter.next = pl->conf->filters;
pl->conf->filters = &transparent_filter;
}
transparent_filter.filter_open = NULL;
transparent_mode = TRANSPARENT_AUTO;
}
#endif

View File

@ -1,7 +1,5 @@
"""dnspr: a caching DNS proxy, answering from what it has been told.""" """dnspr: a caching DNS proxy, answering from what it has been told."""
import time
def run(t): def run(t):
port = t.free_port() port = t.free_port()
@ -17,11 +15,8 @@ def run(t):
allow * allow *
dnspr -p{port} dnspr -p{port}
""") """)
# Wait for the service: a datagram sent too early is simply lost. Bound # wait for the service: a datagram sent too early is simply lost
# by the clock, not by a number of attempts, so a server that answers for _ in range(100):
# nothing costs seconds rather than minutes.
deadline = time.time() + 5
while time.time() < deadline:
if t.dns_query(port, "host.test"): if t.dns_query(port, "host.test"):
break break

View File

@ -190,23 +190,15 @@ def run(t):
t.ne(200, t.http(v4url, proxy=f"127.0.0.1:{partial}").status, t.ne(200, t.http(v4url, proxy=f"127.0.0.1:{partial}").status,
"denying 127.0.0.1 denies the address as written") "denying 127.0.0.1 denies the address as written")
t.eq(200, t.http(mapped, proxy=f"127.0.0.1:{partial}").status,
# Whether the mapped form reaches the same host is up to the stack: it "but the same host asked for as ::ffff:127.0.0.1 is still reached")
# does where a mapped address is routed to IPv4, and that is the hazard
# the security notes describe. Where it does not, there is nothing to
# assert, but the rule that names every spelling still has to hold.
if t.http(mapped, proxy=f"127.0.0.1:{partial}").status == 200:
t.ok("the same host asked for as ::ffff:127.0.0.1 is still reached")
else:
t.skip("the mapped form (this stack does not route it to IPv4)")
t.eq(200, t.http(url, proxy=f"127.0.0.1:{partial}").status, t.eq(200, t.http(url, proxy=f"127.0.0.1:{partial}").status,
"and ::1 is reached, which the rule never mentioned") "and so is ::1, which the rule never mentioned")
t.ne(200, t.http(v4url, proxy=f"127.0.0.1:{complete}").status, t.ne(200, t.http(v4url, proxy=f"127.0.0.1:{complete}").status,
"naming every spelling denies the plain address") "naming every spelling denies the plain address")
t.ne(200, t.http(mapped, proxy=f"127.0.0.1:{complete}").status, t.ne(200, t.http(mapped, proxy=f"127.0.0.1:{complete}").status,
"and the mapped one, whether or not it would have been reachable") "and the mapped one")
t.ne(200, t.http(url, proxy=f"127.0.0.1:{complete}").status, t.ne(200, t.http(url, proxy=f"127.0.0.1:{complete}").status,
"and the IPv6 loopback") "and the IPv6 loopback")

View File

@ -1,164 +0,0 @@
"""Transparent proxying: the destination comes from the redirection.
A redirected connection no longer says where it was going, so the proxy has
to ask the packet filter. That means a real redirection rule, which needs
privilege, so the case skips unless it can install one and remove it again.
The rule must not catch the proxy's own connection to the origin, or the
traffic goes round for ever. Here the proxy is given an outgoing address of
its own and the rule excludes it, which is the arrangement the documentation
recommends.
"""
import os
import platform
import shutil
import subprocess
ORIGIN_ADDR = "127.0.0.9" # where the client believes it is going
PROXY_ADDR = "127.0.0.8" # the source the proxy connects from
DECOY_ADDR = "127.0.0.7" # a second server, to show where traffic went
def _run(command):
done = subprocess.run(command, stdout=subprocess.PIPE,
stderr=subprocess.STDOUT, timeout=30)
return done.returncode, done.stdout.decode("utf-8", "replace").strip()
def _iptables_rule(action, origin_port, proxy_port):
return ["iptables", "-t", "nat", action, "OUTPUT",
"-p", "tcp", "-d", ORIGIN_ADDR, "--dport", str(origin_port),
"!", "-s", PROXY_ADDR,
"-j", "REDIRECT", "--to-ports", str(proxy_port)]
def _config(t, name, body):
"""Run a configuration that has no service, and return what it said."""
return t.run_config(name, body + "\nnot_a_command\n")
def run(t):
# --- the command and its modes ------------------------------------
# These need no redirection, so they run wherever the feature is built.
out = _config(t, "transparent_probe", "log\ntransparent")
if "'transparent'" in out:
t.skip("transparent proxying (not built in this configuration)")
return
for mode in ("auto", "socket"):
t.not_contains(_config(t, "mode_" + mode, f"log\ntransparent {mode}"),
"transparent:", f"the {mode} mode is accepted")
t.contains(_config(t, "mode_bogus", "log\ntransparent bogus"),
"unknown mode", "an unknown mode is refused")
# A mode the build has no code for is refused rather than ignored, so a
# configuration written for another platform fails where it is wrong
# instead of quietly doing something else.
for mode, built in (("netfilter", platform.system() == "Linux"), ("pf", False)):
out = _config(t, "mode_" + mode, f"log\ntransparent {mode}")
if built:
t.not_contains(out, "not available", f"the {mode} mode is accepted where it exists")
elif "not available" in out:
t.ok(f"the {mode} mode is refused where it does not exist")
else:
t.skip(f"the {mode} mode (built here, nothing to check)")
t.not_contains(_config(t, "notransparent", "log\ntransparent\nnotransparent"),
"'notransparent'", "notransparent is accepted")
# A configuration written for the plugin still loads: the line that used
# to load it is accepted and does nothing, the way the ssl and pcre ones
# are, so an existing configuration does not have to be edited first.
out = _config(t, "plugin_line",
"log\nplugin /usr/local/lib/TransparentPlugin.ld.so transparent_plugin")
t.not_contains(out, "failed", "loading the old plugin is accepted and ignored")
t.contains(_config(t, "plugin_missing", "log\nplugin /nope/NoSuch.so nosuch_plugin"),
"failed", "an unknown plugin still fails to load")
# --- and the redirection itself ------------------------------------
if platform.system() != "Linux":
# The BSDs need a redirection that leaves the original destination on
# the socket - divert-to on OpenBSD, ipfw fwd on FreeBSD - and macOS
# has neither, so there is nothing to set up here.
t.skip(f"transparent proxying (no redirection to set up on {platform.system()})")
return
if os.geteuid() != 0 or not shutil.which("iptables"):
t.skip("transparent proxying (needs root and iptables to redirect)")
return
origin_port = t.free_port()
decoy_port = t.free_port()
mapper_port = t.free_port()
plain_port = t.free_port()
t.start("transparent", f"""
log
auth iponly
allow *
http * /echo* echo
httpsrv -p{origin_port} -i{ORIGIN_ADDR}
# a second server, to tell apart where a connection actually went
flush
auth iponly
allow *
http * * data size=13
httpsrv -p{decoy_port} -i{DECOY_ADDR}
# a port mapper aimed at the decoy: with the destination taken from
# the redirection instead, it goes to the origin
flush
auth iponly
allow *
transparent
tcppm -e{PROXY_ADDR} {mapper_port} {DECOY_ADDR} {decoy_port}
notransparent
# the same mapper without it, which keeps going to the decoy
flush
auth iponly
allow *
tcppm -e{PROXY_ADDR} {plain_port} {DECOY_ADDR} {decoy_port}
""", ports=[(ORIGIN_ADDR, origin_port), (DECOY_ADDR, decoy_port),
mapper_port, plain_port])
code, out = _run(_iptables_rule("-A", origin_port, mapper_port))
if code:
t.skip(f"transparent proxying (could not add a redirect rule: {out})")
return
try:
# The client asks for the address it wants; the rule sends the
# connection to the mapper instead, and the mapper has to work out
# where it was headed.
r = t.http(f"http://{ORIGIN_ADDR}:{origin_port}/echo")
t.eq(200, r.status, "a redirected connection reaches its destination")
t.contains(r, "path=/echo", "the request arrives unchanged")
t.contains(r, f"host={ORIGIN_ADDR}:{origin_port}",
"the client still believes it is talking to the origin")
t.contains(r, f"peer.addr={PROXY_ADDR}",
"the origin is reached from the proxy's own address")
# That address is what the rule excludes, which is what stops the
# proxy's own connection from being redirected back into itself.
t.not_contains(r, "size=13", "the connection did not go to the decoy")
# Without the command the mapper has no reason to look, and goes
# where it was configured to go.
_run(_iptables_rule("-D", origin_port, mapper_port))
code, out = _run(_iptables_rule("-A", origin_port, plain_port))
if code:
t.skip("the mapper without the command (could not move the rule)")
else:
r = t.http(f"http://{ORIGIN_ADDR}:{origin_port}/echo")
t.eq(13, r.length,
"without the command the connection goes to the configured target")
t.not_contains(r, "path=/echo",
"and never reaches the address the client asked for")
_run(_iptables_rule("-D", origin_port, plain_port))
finally:
# leave the machine as it was found, whatever happened above
_run(_iptables_rule("-D", origin_port, mapper_port))
_run(_iptables_rule("-D", origin_port, plain_port))

View File

@ -424,20 +424,15 @@ class Tester:
# ---- DNS --------------------------------------------------------- # ---- DNS ---------------------------------------------------------
def dns_query(self, port, name, host="127.0.0.1", timeout=2.0): def dns_query(self, port, name, host="127.0.0.1"):
"""Ask for an A record and return the addresses in the answer. """Ask for an A record and return the addresses in the answer."""
The default timeout is short: a name server on the loopback answers
at once or not at all, and waiting the full request timeout on every
attempt turns a server that answers nothing into a very slow run.
"""
query = struct.pack("!HHHHHH", 0x2A2A, 0x0100, 1, 0, 0, 0) query = struct.pack("!HHHHHH", 0x2A2A, 0x0100, 1, 0, 0, 0)
for label in name.split("."): for label in name.split("."):
query += bytes([len(label)]) + label.encode() query += bytes([len(label)]) + label.encode()
query += b"\x00" + struct.pack("!HH", 1, 1) query += b"\x00" + struct.pack("!HH", 1, 1)
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.settimeout(timeout) sock.settimeout(self.timeout)
try: try:
sock.sendto(query, (host, port)) sock.sendto(query, (host, port))
data = sock.recvfrom(65536)[0] data = sock.recvfrom(65536)[0]