tcptoudp, udptotcp: tunnels between TCP and UDP
Some checks are pending
C/C++ CI Linux / ${{ matrix.target }} (ubuntu-24.04-arm) (push) Waiting to run
C/C++ CI Linux / ${{ matrix.target }} (ubuntu-latest) (push) Waiting to run
C/C++ CI MacOS / ${{ matrix.target }} (macos-15) (push) Waiting to run
C/C++ CI Windows / ${{ matrix.target }} (windows-2022) (push) Waiting to run
C/C++ CI cmake / ${{ matrix.target }} (macos-15) (push) Waiting to run
C/C++ CI cmake / ${{ matrix.target }} (ubuntu-24.04-arm) (push) Waiting to run
C/C++ CI cmake / ${{ matrix.target }} (ubuntu-latest) (push) Waiting to run
C/C++ CI cmake / ${{ matrix.target }} (windows-2022) (push) Waiting to run
C/C++ CI cmake / ubuntu-latest (wolfSSL) (push) Waiting to run

Datagrams on a TCP connection, each with a two byte length in network
order in front of it, which is how udptunnel (the rfc1036 one) and
ProxyGuard (eduVPN) put them there, the framing alone or after a
handshake: udptunnel's 32 bytes, or ProxyGuard's HTTP Upgrade for
UoTLV/1 answered with 101 Switching Protocols.

tcptoudp takes the connections, a UDP socket of its own to the target
for each, the handshake a client speaks recognised by its first bytes
and answered; a parent the ACL gives is a SOCKS5 UDP relay, as for
udppm. udptotcp takes the datagrams, each client a session of the
service's with a TCP connection of its own, connected as the service's
loop sees it writable (dnspr asks a name server over TCP the same way);
-m sends udptunnel's handshake to the target, -m2 ProxyGuard's, -m0
(the default) none, so both ends of either tool are stood in for.

The service names take two places of the string table, the default
configuration path moving two later (stringtable[27]).

Built by the Makefiles and CMake, with the standalone binaries, and
installed by them; a case of the test suite speaks all three handshakes.
This commit is contained in:
Vladimir Dubrovin 2026-11-15 16:02:01 +03:00
parent e18cd526bc
commit f4dca5d462
21 changed files with 1044 additions and 28 deletions

View File

@ -45,6 +45,8 @@ jobs:
cp -f doc/html/man8/tlspr.8.html wiki/tlspr.md
cp -f doc/html/man8/tcppm.8.html wiki/tcppm.md
cp -f doc/html/man8/udppm.8.html wiki/udppm.md
cp -f doc/html/man8/tcptoudp.8.html wiki/tcptoudp.md
cp -f doc/html/man8/udptotcp.8.html wiki/udptotcp.md
cp -f doc/html/man5/3proxy.cfg.5.html wiki/3proxy.cfg.md
cp -f doc/html/highload.html wiki/High-Load.md
cp -f doc/html/howtoe.html wiki/How-To.md

2
.gitignore vendored
View File

@ -268,8 +268,10 @@ bin/3proxy_proxy
bin/3proxy_smtpp
bin/3proxy_socks
bin/3proxy_tcppm
bin/3proxy_tcptoudp
bin/3proxy_tlspr
bin/3proxy_udppm
bin/3proxy_udptotcp
build*/*
testcerts/
/.claude

View File

@ -94,10 +94,12 @@ option(3PROXY_BUILD_SMTPP "Build standalone smtpp binary" OFF)
option(3PROXY_BUILD_FTPPR "Build standalone ftppr binary" OFF)
option(3PROXY_BUILD_TCPPM "Build standalone tcppm binary" ON)
option(3PROXY_BUILD_UDPPM "Build standalone udppm binary" ON)
option(3PROXY_BUILD_TCPTOUDP "Build standalone tcptoudp binary" ON)
option(3PROXY_BUILD_UDPTOTCP "Build standalone udptotcp binary" ON)
option(3PROXY_BUILD_TLSPR "Build standalone tlspr binary" ON)
if(3PROXY_BUILD_NONE)
foreach(_M PROXY SOCKS POP3P IMAPP SMTPP FTPPR TCPPM UDPPM TLSPR)
foreach(_M PROXY SOCKS POP3P IMAPP SMTPP FTPPR TCPPM UDPPM TCPTOUDP UDPTOTCP TLSPR)
set(3PROXY_BUILD_${_M} OFF)
endforeach()
endif()
@ -447,6 +449,7 @@ add_library(srv_modules OBJECT
src/smtpp.c
src/ftppr.c
src/tcppm.c
src/tcptoudp.c
src/tlspr.c
src/auto.c
src/socks.c
@ -464,6 +467,12 @@ target_include_directories(srvudppm_obj PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src
)
# UDP to TCP tunnel server module (without WITHMAIN)
add_library(srvudptotcp_obj OBJECT src/udptotcp.c)
target_include_directories(srvudptotcp_obj PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src
)
# UDP socket relay (used by 3proxy, socks, udppm)
add_library(udpsockmap_obj OBJECT src/udpsockmap.c)
target_include_directories(udpsockmap_obj PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
@ -497,6 +506,7 @@ add_executable(3proxy
${3PROXY_CORE_SOURCES}
$<TARGET_OBJECTS:srv_modules>
$<TARGET_OBJECTS:srvudppm_obj>
$<TARGET_OBJECTS:srvudptotcp_obj>
$<TARGET_OBJECTS:mainfunc>
$<TARGET_OBJECTS:common_obj>
$<TARGET_OBJECTS:udpsockmap_obj>
@ -689,7 +699,7 @@ else()
endif()
# Build standalone proxy executables
foreach(PROXY_NAME proxy socks pop3p imapp smtpp ftppr tcppm udppm tlspr)
foreach(PROXY_NAME proxy socks pop3p imapp smtpp ftppr tcppm udppm tcptoudp udptotcp tlspr)
string(TOUPPER "${PROXY_NAME}" _MODULE_OPT)
if(NOT 3PROXY_BUILD_${_MODULE_OPT})
continue()
@ -728,8 +738,8 @@ foreach(PROXY_NAME proxy socks pop3p imapp smtpp ftppr tcppm udppm tlspr)
target_compile_definitions(${PROXY_NAME} PRIVATE WITHMAIN)
# each takes the main loop of its kind only: udppm UDP, the others TCP
if(PROXY_NAME STREQUAL "udppm")
# each takes the main loop of its kind only: udppm and udptotcp UDP, the others TCP
if(PROXY_NAME STREQUAL "udppm" OR PROXY_NAME STREQUAL "udptotcp")
target_compile_definitions(${PROXY_NAME} PRIVATE NOTCPMAIN)
else()
target_compile_definitions(${PROXY_NAME} PRIVATE NOUDPMAIN)
@ -743,7 +753,7 @@ foreach(PROXY_NAME proxy socks pop3p imapp smtpp ftppr tcppm udppm tlspr)
# port mappers take port, host and port as arguments, the others options
# only; one of the two is defined, as in src/Makefile.inc
if(PROXY_NAME STREQUAL "tcppm" OR PROXY_NAME STREQUAL "udppm" OR PROXY_NAME STREQUAL "tlspr")
if(PROXY_NAME STREQUAL "tcppm" OR PROXY_NAME STREQUAL "udppm" OR PROXY_NAME STREQUAL "tcptoudp" OR PROXY_NAME STREQUAL "udptotcp" OR PROXY_NAME STREQUAL "tlspr")
target_compile_definitions(${PROXY_NAME} PRIVATE PORTMAP)
else()
target_compile_definitions(${PROXY_NAME} PRIVATE NOPORTMAP)
@ -757,11 +767,11 @@ foreach(PROXY_NAME proxy socks pop3p imapp smtpp ftppr tcppm udppm tlspr)
target_sources(${PROXY_NAME} PRIVATE $<TARGET_OBJECTS:base64_obj>)
endif()
if(PROXY_NAME STREQUAL "udppm")
if(PROXY_NAME STREQUAL "udppm" OR PROXY_NAME STREQUAL "udptotcp")
target_sources(${PROXY_NAME} PRIVATE src/hash.c)
endif()
if(PROXY_NAME STREQUAL "socks" OR PROXY_NAME STREQUAL "udppm")
if(PROXY_NAME STREQUAL "socks" OR PROXY_NAME STREQUAL "udppm" OR PROXY_NAME STREQUAL "tcptoudp")
target_sources(${PROXY_NAME} PRIVATE src/udpsockmap.c)
endif()
endforeach()
@ -797,7 +807,7 @@ install(TARGETS 3proxy 3proxy_crypt
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
foreach(PROXY_NAME proxy socks pop3p imapp smtpp ftppr tcppm udppm tlspr)
foreach(PROXY_NAME proxy socks pop3p imapp smtpp ftppr tcppm udppm tcptoudp udptotcp tlspr)
string(TOUPPER "${PROXY_NAME}" _MODULE_OPT)
if(3PROXY_BUILD_${_MODULE_OPT})
install(TARGETS ${PROXY_NAME}

View File

@ -117,7 +117,7 @@ install: all
if [ ! -d "$(DESTDIR)$(BINPREFIX)" ]; then mkdir -p $(DESTDIR)$(BINPREFIX)/; fi
install bin/3proxy $(DESTDIR)$(BINPREFIX)/3proxy
install bin/$(CRYPT_PREFIX)crypt $(DESTDIR)$(BINPREFIX)/$(CRYPT_PREFIX)crypt
for f in proxy socks pop3p imapp smtpp ftppr tcppm udppm tlspr; do \
for f in proxy socks pop3p imapp smtpp ftppr tcppm udppm tcptoudp udptotcp tlspr; do \
if [ -f bin/$(PREFIX)$$f ]; then install bin/$(PREFIX)$$f $(DESTDIR)$(BINPREFIX)/$(PREFIX)$$f; fi; \
done
install -d $(DESTDIR)$(prefix)/etc/3proxy/conf
@ -134,7 +134,7 @@ install: all
touch $(DESTDIR)$(prefix)/etc/3proxy/conf/bandlimiters
install -d $(DESTDIR)$(MANDIR)/man8
install -m 644 man/3proxy.8 $(DESTDIR)$(MANDIR)/man8/3proxy.8
for f in proxy socks pop3p imapp smtpp ftppr tcppm udppm tlspr; do \
for f in proxy socks pop3p imapp smtpp ftppr tcppm udppm tcptoudp udptotcp tlspr; do \
if [ -f man/$$f.8 ] && [ -f bin/$(PREFIX)$$f ]; then install -m 644 man/$$f.8 $(DESTDIR)$(MANDIR)/man8/$(PREFIX)$$f.8; fi; \
done
install -m 644 man/3proxy_crypt.8 $(DESTDIR)$(MANDIR)/man8/$(CRYPT_PREFIX)crypt.8

View File

@ -126,8 +126,10 @@ INSTALL_OBJS = bin/3proxy \
bin/$(PREFIX)proxy \
bin/$(PREFIX)socks \
bin/$(PREFIX)tcppm \
bin/$(PREFIX)tcptoudp \
bin/$(PREFIX)tlspr \
bin/$(PREFIX)udppm
bin/$(PREFIX)udppm \
bin/$(PREFIX)udptotcp
# the mail proxies and ftppr are only there when they were built
ifeq ($(MAILPROXY),true)
INSTALL_OBJS += bin/$(PREFIX)imapp bin/$(PREFIX)pop3p bin/$(PREFIX)smtpp
@ -191,7 +193,7 @@ install-man:
$(INSTALL_BIN) -d $(MANDIR8)
$(INSTALL_DATA) man/3proxy.cfg.5 $(MANDIR5)
$(INSTALL_DATA) man/3proxy.8 $(MANDIR8)
for f in proxy socks pop3p imapp smtpp ftppr tcppm udppm tlspr; do \
for f in proxy socks pop3p imapp smtpp ftppr tcppm udppm tcptoudp udptotcp tlspr; do \
if [ -f man/$$f.8 ] && [ -f bin/$(PREFIX)$$f ]; then $(INSTALL_DATA) man/$$f.8 $(MANDIR8)/$(PREFIX)$$f.8; fi; \
done
$(INSTALL_DATA) man/3proxy_crypt.8 $(MANDIR8)/$(CRYPT_PREFIX)crypt.8

View File

@ -32,6 +32,8 @@ from the configuration file like an independent service
.BR imapp (8)
.BR tcppm (8)
.BR udppm (8)
.BR tcptoudp (8)
.BR udptotcp (8)
.BR ftppr (8)
.BR dnspr
but
@ -161,7 +163,7 @@ configuration file
Report all bugs to
.BR 3proxy@3proxy.org
.SH SEE ALSO
3proxy.cfg(5), proxy(8), ftppr(8), socks(8), pop3p(8), imapp(8), tcppm(8), udppm(8),
3proxy.cfg(5), proxy(8), ftppr(8), socks(8), pop3p(8), imapp(8), tcppm(8), udppm(8), tcptoudp(8), udptotcp(8),
kill(1), syslogd(8),
.br
https://3proxy.org/

View File

@ -79,6 +79,14 @@ Recursion is not allowed.
.B udppm
[options]
\fI<SRCPORT>\fR \fI<DSTADDR>\fR \fI<DSTPORT>\fR
.br
.B tcptoudp
[options]
\fI<SRCPORT>\fR \fI<DSTADDR>\fR \fI<DSTPORT>\fR
.br
.B udptotcp
[options]
\fI<SRCPORT>\fR \fI<DSTADDR>\fR \fI<DSTPORT>\fR
.br
Descriptions:
.br
@ -127,6 +135,25 @@ but must be specified for syntax compatibility.
.B udppm
UDP portmapper
.br
.B tcptoudp
TCP to UDP tunnel: connections on SRCPORT carry datagrams, each with a
two byte length in network order in front of it, sent as UDP datagrams
to DSTADDR:DSTPORT, the answers framed the same way back. The handshake
a client speaks \(em udptunnel's (the rfc1036 one) or ProxyGuard's HTTP
Upgrade for UoTLV/1 (eduVPN) \(em is recognised and answered; a
connection that begins with a length needs none. Each connection has a
UDP socket of its own to the target.
.br
.B udptotcp
UDP to TCP tunnel: each client sending datagrams to SRCPORT is a session
of the service with a TCP connection of its own to DSTADDR:DSTPORT, its
datagrams sent on it with a two byte length in front of each, what comes
back answered to the client. \fB-m\fR sends udptunnel's handshake to the
target first, \fB-m2\fR ProxyGuard's (an HTTP Upgrade for UoTLV/1), \fB-m0\fR
sends none, which is the default.
.br
Options:
.br
@ -2029,7 +2056,7 @@ the tree is refused outright.
Report all bugs to
.BR 3proxy@3proxy.org
.SH SEE ALSO
3proxy(8), 3proxy_crypt(8), proxy(8), ftppr(8), socks(8), pop3p(8), imapp(8), smtpp(8), tlspr(8), tcppm(8), udppm(8), syslogd(8),
3proxy(8), 3proxy_crypt(8), proxy(8), ftppr(8), socks(8), pop3p(8), imapp(8), smtpp(8), tlspr(8), tcppm(8), udppm(8), tcptoudp(8), udptotcp(8), syslogd(8),
.br
https://3proxy.org/
.SH AUTHORS

99
man/tcptoudp.8 Normal file
View File

@ -0,0 +1,99 @@
.TH tcptoudp "8" "November 2026" "3proxy" "Universal proxy server"
.SH NAME
.B tcptoudp
\- TCP to UDP tunnel
.SH SYNOPSIS
.BR "tcptoudp " [ -d ]
.IB \fR[ -l \fR[ \fR[ @ \fR] logfile \fR]]
.IB \fR[ -i internal_ip\fR]
.IB \fR[ -e external_ip\fR]
.I local_port remote_host remote_port
.SH DESCRIPTION
.B tcptoudp
takes TCP connections on
.I local_port
and sends what they carry as datagrams to
.IR remote_host : remote_port ,
framing each datagram with its length on the connection. This is the
server side of udptunnel (the rfc1036 one, started with \fB-s\fR) and of
ProxyGuard (eduVPN), whose clients it answers: a connection that begins
with udptunnel's 32 byte handshake has it taken off, one that asks with
an HTTP Upgrade for UoTLV/1 is told the protocols are switched, and one
that begins with a length goes to the framing at once. What comes back
from the target is sent on the connection framed the same way. Each
connection has a UDP socket of its own to the target; a parent the ACL
gives is a SOCKS5 UDP relay, as for udppm.
.SH OPTIONS
.TP
.B -I
Inetd mode. Standalone service only.
.TP
.B -d
Daemonize. Detach service from console and run in the background.
.TP
.B -t
Be silenT. Do not log start/stop/accept error records.
.TP
.B -e
External address. IP address of the interface the proxy should send datagrams
from.
By default, the system will decide which address to use in accordance
with the routing table.
.TP
.B \-ni\fIPATH\fR
(Linux only) Switch to the network namespace identified by
.I PATH
before opening the listening socket. The current namespace is saved and restored
immediately after binding, so outgoing datagrams run in the original namespace
unless
.B \-ne
is also given.
.TP
.B \-ne\fIPATH\fR
(Linux only) Switch to the network namespace identified by
.I PATH
after the listening socket has been bound (and after restoring from
.B \-ni
if applicable). Both options accept any namespace file path
(e.g.\& \fI/var/run/netns/myns\fR or \fI/proc/PID/ns/net\fR)
and require \fBCAP_SYS_ADMIN\fR.
.TP
.B -i
Internal address. IP address the proxy accepts connections to.
By default, connections to any interface are accepted. It\'s usually unsafe.
.TP
.B -l
Log. By default logging is to stdout. If
.I logfile
is specified logging is to file. Under Unix, if
.RI \' @ \'
precedes
.IR logfile ,
syslog is used for logging.
.TP
.B -S
Increase or decrease stack size. You may want to try something like -S8192 if you experience 3proxy
crashes.
.SH ARGUMENTS
.TP
.I local_port
- port tcptoudp accepts connections on
.TP
.I remote_host
- IP address of the host datagrams are sent to
.TP
.I remote_port
- remote port datagrams are sent to
.SH CLIENTS
udptunnel's client (without \fB-s\fR), ProxyGuard's client
(\fBproxyguard-client\fR), or anything that puts datagrams on a TCP
connection with a two byte length in network order in front of each.
.SH BUGS
Report all bugs to
.BR 3proxy@3proxy.org
.SH SEE ALSO
3proxy(8), 3proxy.cfg(5), udptotcp(8), tcppm(8), udppm(8), syslogd(8),
.br
https://3proxy.org/
.SH AUTHORS
3proxy is designed by Vladimir Dubrovin <vlad@3proxy.org>

113
man/udptotcp.8 Normal file
View File

@ -0,0 +1,113 @@
.TH udptotcp "8" "November 2026" "3proxy" "Universal proxy server"
.SH NAME
.B udptotcp
\- UDP to TCP tunnel
.SH SYNOPSIS
.BR "udptotcp " [ -d ", " \fR[ -m \fR| \fR -m2 \fR| \fR -m0 \fR] ]
.IB \fR[ -l \fR[ \fR[ @ \fR] logfile \fR]]
.IB \fR[ -i internal_ip\fR]
.IB \fR[ -e external_ip\fR]
.I local_port remote_host remote_port
.SH DESCRIPTION
.B udptotcp
takes datagrams on
.I local_port
and sends them on a TCP connection to
.IR remote_host : remote_port ,
each with its length in front of it. This is the client side of udptunnel
(the rfc1036 one) and of ProxyGuard (eduVPN): each client is a session of
the service with a TCP connection of its own, and what comes back on the
connection is answered to that client as datagrams. A parent the ACL
gives is asked for a TCP connection to the target, not a UDP relay as
udppm's.
.SH OPTIONS
.TP
.B -I
Inetd mode. Standalone service only.
.TP
.B -d
Daemonize. Detach service from console and run in the background.
.TP
.B -t
Be silenT. Do not log start/stop/accept error records.
.TP
.B -e
External address. IP address of the interface the proxy should initiate the
TCP connection from.
By default, the system will decide which address to use in accordance
with the routing table.
.TP
.B \-ni\fIPATH\fR
(Linux only) Switch to the network namespace identified by
.I PATH
before opening the listening socket. The current namespace is saved and restored
immediately after binding, so the TCP connection runs in the original namespace
unless
.B \-ne
is also given.
.TP
.B \-ne\fIPATH\fR
(Linux only) Switch to the network namespace identified by
.I PATH
after the listening socket has been bound (and after restoring from
.B \-ni
if applicable). Both options accept any namespace file path
(e.g.\& \fI/var/run/netns/myns\fR or \fI/proc/PID/ns/net\fR)
and require \fBCAP_SYS_ADMIN\fR.
.TP
.B -i
Internal address. IP address the proxy accepts datagrams to.
By default, datagrams to any interface are accepted. It\'s usually unsafe.
.TP
.B -l
Log. By default logging is to stdout. If
.I logfile
is specified logging is to file. Under Unix, if
.RI \' @ \'
precedes
.IR logfile ,
syslog is used for logging.
.TP
.B -m
udptunnel's handshake: the 32 bytes a client of udptunnel sends are sent
to the target before the datagrams.
.TP
.B -m2
ProxyGuard's handshake: an HTTP Upgrade for UoTLV/1 is asked of the
target, and the datagrams begin once the target has switched protocols.
.TP
.B -m0
No handshake: the datagrams are sent with none at all, which tcptoudp
and ProxyGuard's server also take. This is the default.
.TP
.B -S
Increase or decrease stack size. You may want to try something like -S8192 if you experience 3proxy
crashes.
.SH ARGUMENTS
.TP
.I local_port
- port udptotcp accepts datagrams on
.TP
.I remote_host
- IP address of the host the TCP connection is made to
.TP
.I remote_port
- remote port the TCP connection is made to
.SH CLIENTS
Any UDP-based application can be used as a client. Use
.I internal_ip
and
.I local_port
as the destination in the client application. The datagrams are carried
to
.IR remote_host : remote_port
and the answers come back.
.SH BUGS
Report all bugs to
.BR 3proxy@3proxy.org
.SH SEE ALSO
3proxy(8), 3proxy.cfg(5), tcptoudp(8), tcppm(8), udppm(8), syslogd(8),
.br
https://3proxy.org/
.SH AUTHORS
3proxy is designed by Vladimir Dubrovin <vlad@3proxy.org>

View File

@ -39,8 +39,10 @@ make clean
/bin/3proxy_proxy
/bin/3proxy_socks
/bin/3proxy_tcppm
/bin/3proxy_tcptoudp
/bin/3proxy_tlspr
/bin/3proxy_udppm
/bin/3proxy_udptotcp
/bin/add3proxyuser
%config(noreplace) /etc/3proxy/3proxy.cfg
/etc/3proxy/conf

View File

@ -29,7 +29,7 @@ void httpsrv_init(void);
#endif
#ifndef DEFAULTCONFIG
#define DEFAULTCONFIG conf->stringtable[25]
#define DEFAULTCONFIG conf->stringtable[27]
#endif
FILE * confopen();

View File

@ -5,7 +5,7 @@ PROXYMAIN = proxymain.c mainconf.c mainlisten.c mainclient.c maintcp.c mainudp.c
# 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) $(MAIL_EXES) $(FTP_EXES) $(BUILDDIR)$(PREFIX)tcppm$(EXESUFFICS) $(BUILDDIR)$(PREFIX)udppm$(EXESUFFICS) $(BUILDDIR)$(PREFIX)tcptoudp$(EXESUFFICS) $(BUILDDIR)$(PREFIX)udptotcp$(EXESUFFICS) $(BUILDDIR)$(PREFIX)tlspr$(EXESUFFICS) $(BUILDDIR)$(PREFIX)socks$(EXESUFFICS) $(BUILDDIR)$(PREFIX)proxy$(EXESUFFICS) allplugins
sockmap$(OBJSUFFICS): sockmap.c proxy.h structures.h
$(CC) $(CFLAGS) sockmap.c
@ -52,6 +52,12 @@ tcppm$(OBJSUFFICS): tcppm.c proxy.h structures.h $(PROXYMAIN)
udppm$(OBJSUFFICS): udppm.c proxy.h structures.h $(PROXYMAIN)
$(CC) $(CFLAGS) $(DEFINEOPTION)WITHMAIN $(DEFINEOPTION)PORTMAP $(DEFINEOPTION)NOTCPMAIN udppm.c
tcptoudp$(OBJSUFFICS): tcptoudp.c proxy.h structures.h udptunnel.h $(PROXYMAIN)
$(CC) $(CFLAGS) $(DEFINEOPTION)WITHMAIN $(DEFINEOPTION)PORTMAP $(DEFINEOPTION)NOUDPMAIN tcptoudp.c
udptotcp$(OBJSUFFICS): udptotcp.c proxy.h structures.h udptunnel.h $(PROXYMAIN)
$(CC) $(CFLAGS) $(DEFINEOPTION)WITHMAIN $(DEFINEOPTION)PORTMAP $(DEFINEOPTION)NOTCPMAIN udptotcp.c
tlspr$(OBJSUFFICS): tlspr.c proxy.h structures.h $(PROXYMAIN)
$(CC) $(CFLAGS) $(DEFINEOPTION)WITHMAIN $(DEFINEOPTION)PORTMAP $(DEFINEOPTION)NOUDPMAIN tlspr.c
@ -86,6 +92,12 @@ $(BUILDDIR)$(PREFIX)tcppm$(EXESUFFICS): sockmap$(OBJSUFFICS) sockgetchar$(OBJSUF
$(BUILDDIR)$(PREFIX)udppm$(EXESUFFICS): sockmap$(OBJSUFFICS) udpsockmap$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) udppm$(OBJSUFFICS) common$(OBJSUFFICS) sem$(OBJSUFFICS) log$(OBJSUFFICS) hash$(OBJSUFFICS)
$(LN) $(LNOUT)$(BUILDDIR)$(PREFIX)udppm$(EXESUFFICS) $(LDFLAGS) sockmap$(OBJSUFFICS) udpsockmap$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) udppm$(OBJSUFFICS) log$(OBJSUFFICS) common$(OBJSUFFICS) sem$(OBJSUFFICS) hash$(OBJSUFFICS) $(LIBS)
$(BUILDDIR)$(PREFIX)tcptoudp$(EXESUFFICS): sockmap$(OBJSUFFICS) udpsockmap$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) tcptoudp$(OBJSUFFICS) common$(OBJSUFFICS) sem$(OBJSUFFICS) log$(OBJSUFFICS)
$(LN) $(LNOUT)$(BUILDDIR)$(PREFIX)tcptoudp$(EXESUFFICS) $(LDFLAGS) sockmap$(OBJSUFFICS) udpsockmap$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) tcptoudp$(OBJSUFFICS) log$(OBJSUFFICS) common$(OBJSUFFICS) sem$(OBJSUFFICS) $(LIBS)
$(BUILDDIR)$(PREFIX)udptotcp$(EXESUFFICS): sockmap$(OBJSUFFICS) udpsockmap$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) udptotcp$(OBJSUFFICS) common$(OBJSUFFICS) sem$(OBJSUFFICS) log$(OBJSUFFICS) hash$(OBJSUFFICS)
$(LN) $(LNOUT)$(BUILDDIR)$(PREFIX)udptotcp$(EXESUFFICS) $(LDFLAGS) sockmap$(OBJSUFFICS) udpsockmap$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) udptotcp$(OBJSUFFICS) log$(OBJSUFFICS) common$(OBJSUFFICS) sem$(OBJSUFFICS) hash$(OBJSUFFICS) $(LIBS)
$(BUILDDIR)$(PREFIX)tlspr$(EXESUFFICS): sockmap$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) tlspr$(OBJSUFFICS) common$(OBJSUFFICS) sem$(OBJSUFFICS) log$(OBJSUFFICS)
$(LN) $(LNOUT)$(BUILDDIR)$(PREFIX)tlspr$(EXESUFFICS) $(LDFLAGS) sockmap$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) tlspr$(OBJSUFFICS) log$(OBJSUFFICS) common$(OBJSUFFICS) sem$(OBJSUFFICS) $(LIBS)
@ -131,6 +143,12 @@ srvhttpsrv$(OBJSUFFICS): httpsrv.c proxy.h structures.h
srvudppm$(OBJSUFFICS): udppm.c proxy.h structures.h
$(CC) $(COUT)srvudppm$(OBJSUFFICS) $(CFLAGS) udppm.c
srvtcptoudp$(OBJSUFFICS): tcptoudp.c proxy.h structures.h udptunnel.h
$(CC) $(COUT)srvtcptoudp$(OBJSUFFICS) $(CFLAGS) tcptoudp.c
srvudptotcp$(OBJSUFFICS): udptotcp.c proxy.h structures.h udptunnel.h
$(CC) $(COUT)srvudptotcp$(OBJSUFFICS) $(CFLAGS) udptotcp.c
srvdnspr$(OBJSUFFICS): dnspr.c proxy.h structures.h
$(CC) $(COUT)srvdnspr$(OBJSUFFICS) $(CFLAGS) dnspr.c
@ -200,6 +218,7 @@ ssl$(OBJSUFFICS): ssl.c structures.h proxy.h ssl.h
pcre$(OBJSUFFICS): pcre.c structures.h
$(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) sem$(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)
$(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) sem$(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)
$(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) srvtcptoudp$(OBJSUFFICS) srvudptotcp$(OBJSUFFICS) sockmap$(OBJSUFFICS) udpsockmap$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) common$(OBJSUFFICS) sem$(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)
$(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) srvtcptoudp$(OBJSUFFICS) srvudptotcp$(OBJSUFFICS) sockmap$(OBJSUFFICS) udpsockmap$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) common$(OBJSUFFICS) sem$(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)

View File

@ -31,7 +31,7 @@ void transparent_install(void);
#endif
#ifndef DEFAULTCONFIG
#define DEFAULTCONFIG conf->stringtable[25]
#define DEFAULTCONFIG conf->stringtable[27]
#endif
_3proxy_mutex_t bandlim_mutex;
@ -308,6 +308,21 @@ static int h_proxy(int argc, unsigned char ** argv){
childdef.service = S_UDPPM;
childdef.helpmessage = " -s single packet UDP service for request/reply (DNS-like) services\n";
}
else if(!strcmp((char *)argv[0], "tcptoudp")) {
childdef.pf = tcptoudpchild;
childdef.port = 0;
childdef.isudp = 0;
childdef.service = S_TCPTOUDP;
childdef.helpmessage = " the handshake a client speaks, udptunnel's or ProxyGuard's, is answered\n";
}
else if(!strcmp((char *)argv[0], "udptotcp")) {
childdef.pf = NULL;
childdef.udp = &udptotcpservice;
childdef.port = 0;
childdef.isudp = 1;
childdef.service = S_UDPTOTCP;
childdef.helpmessage = " -m - udptunnel handshake with the target, -m2 - ProxyGuard's, -m0 - none\n";
}
#ifdef WITH_HTTPSRV
else if(!strcmp((char *)argv[0], "admin")) {
/* The same service as httpsrv, with the administration pages
@ -2064,6 +2079,8 @@ struct commands commandhandlers[]={
{NULL, "socks", h_proxy, 1, 0},
{NULL, "tcppm", h_proxy, 4, 0},
{NULL, "udppm", h_proxy, 4, 0},
{NULL, "tcptoudp", h_proxy, 4, 0},
{NULL, "udptotcp", h_proxy, 4, 0},
#ifdef WITH_HTTPSRV
{NULL, "admin", h_proxy, 1, 0},
#endif

View File

@ -550,6 +550,12 @@ static int srvprepare(struct srvstart *s, int argc, char **argv){
case 'U':
srv->udpauth = *(argv[i]+2)? atoi(argv[i]+2) : 0;
break;
case 'm':
/* udptotcp: which handshake the target is spoken to with */
if(srv->service == S_UDPTOTCP)
srv->s_option = argv[i][2]? atoi(argv[i]+2) : 1;
else error = 1;
break;
case 'B':
/* the TLS ClientHello split at the SNI (tlspr), which -s is
where -s is not splice; s_option means something else

View File

@ -318,7 +318,7 @@ struct udploop {
uint64_t monotime(void);
int udpanswer(struct udploop *u, struct clientparam *param, unsigned char *buf, int len);
extern const struct udpservice udppmservice, dnsprservice;
extern const struct udpservice udppmservice, dnsprservice, udptotcpservice;
int socks5_setaddr(int family, int atyp, const unsigned char *addr, PROXYSOCKADDRTYPE *sa);
/* which of a service's network namespaces a socket is made in */
#define NS_IN 0 /* -ni: what faces the clients */
@ -561,6 +561,7 @@ void * smtppchild(struct clientparam * param);
void * proxychild(struct clientparam * param);
void * sockschild(struct clientparam * param);
void * tcppmchild(struct clientparam * param);
void * tcptoudpchild(struct clientparam * param);
void * autochild(struct clientparam * param);
#ifdef WITH_HTTPSRV
int op_admin(struct httpreq *r, const unsigned char *params);

View File

@ -32,20 +32,20 @@ unsigned char * strings[] = {
/* 19 */ (unsigned char *)"TLSPR",
/* 20 */ (unsigned char *)"IMAPP",
/* 21 */ (unsigned char *)"HTTPSRV",
/* 22 */ (unsigned char *)"ZOMBIE",
/* 23 */ NULL,
/* 24 */ NULL,
/* 22 */ (unsigned char *)"TCPTOUDP",
/* 23 */ (unsigned char *)"UDPTOTCP",
/* 24 */ (unsigned char *)"ZOMBIE",
/* 25 */ NULL,
/* 26 */ NULL,
#ifndef TPROXY_CONF
#ifndef _WIN32
/* 25 */ (unsigned char *)"/usr/local/etc/3proxy/3proxy.cfg",
/* 27 */ (unsigned char *)"/usr/local/etc/3proxy/3proxy.cfg",
#else
/* 25 */ (unsigned char *)"3proxy.cfg",
/* 27 */ (unsigned char *)"3proxy.cfg",
#endif
#else
/* 25 */ (unsigned char *)TPROXY_CONF,
/* 27 */ (unsigned char *)TPROXY_CONF,
#endif
/* 26 */ NULL,
/* 27 */ NULL,
/* 28 */ NULL,
/* 29 */ NULL,
/* 30 */ NULL,

View File

@ -240,6 +240,8 @@ typedef enum {
S_TLSPR,
S_IMAPP,
S_HTTPSRV,
S_TCPTOUDP,
S_UDPTOTCP,
S_ZOMBIE
}PROXYSERVICE;

230
src/tcptoudp.c Normal file
View File

@ -0,0 +1,230 @@
/*
3APA3A simplest proxy server
(c) 2002-2026 by Vladimir Dubrovin <vlad@3proxy.org>
please read License Agreement
*/
#include "proxy.h"
#include "udptunnel.h"
#ifndef PORTMAP
#define PORTMAP
#endif
#define RETURN(xxx) { param->res = xxx; goto CLEANRET; }
/* tcptoudp: a TCP connection in, datagrams out, to the one target the
service is given. What the connection carries is datagrams with
their length in front of them (udptunnel.h); the handshake a client
speaks, udptunnel's or ProxyGuard's, is recognised and answered. A
parent the ACL gives is a SOCKS UDP relay, as for udppm. */
/* what the connection's first bytes have said, or not yet */
enum { HS_MAYBE, HS_RAW, HS_TUNNEL, HS_HTTP };
/* ProxyGuard's server answer, which ends its handshake */
static const unsigned char tcptoudp101[] =
"HTTP/1.1 101 Switching Protocols\r\n"
"Connection: Upgrade\r\n"
"Upgrade: " UDPTUN_PROTO "\r\n"
"\r\n";
/* A datagram of the client, from buf, on to the target or the first
relay of a chain, the headers of the hops after it and of the target
in front of it: as udppm sends one. */
static void tcptoudpforward(struct clientparam *param, unsigned char *data, int len){
int i, off = 0;
if(param->bandlimfunc && (*param->bandlimfunc)(param, 0, len)) return;
if(param->udp_nhops){
for(i = 1; i < param->udp_nhops; i++)
off += socks5_udp_build_hdr(param->clibuf + off, &param->udp_relay[i-1]);
off += socks5_udp_build_hdr(param->clibuf + off, &param->req);
}
if(len > param->clibufsize - off) len = param->clibufsize - off;
memcpy(param->clibuf + off, data, len);
param->srv->so._sendto(param->sostate, param->remsock, (char *)param->clibuf, off + len, 0,
(struct sockaddr *)&param->sinsr, SASIZE(&param->sinsr));
param->statscli64 += len;
param->nwrites++;
}
/* The datagrams framed in b[0..*len): each on to the target, what is
left of one moved to the front. 0, or the res of a framing no
datagram can be (a length the buffers cannot hold). */
static int tcptoudpframes(struct clientparam *param, unsigned char *b, int *len){
int off = 0, n;
while(*len - off >= UDPTUN_HDR){
n = udptunget(b + off);
if(n > param->srvbufsize - UDPTUN_HDR) return 483;
if(*len - off < UDPTUN_HDR + n) break;
tcptoudpforward(param, b + off + UDPTUN_HDR, n);
off += UDPTUN_HDR + n;
}
if(off){
memmove(b, b + off, *len - off);
*len -= off;
}
return 0;
}
/* Where the end of a handshake's header block was looked for up to */
static int tcptoudphdrend(unsigned char *b, int from, int len){
int i;
for(i = from; i + 3 < len; i++)
if(b[i] == '\r' && b[i+1] == '\n' && b[i+2] == '\r' && b[i+3] == '\n') return i;
return -1;
}
static int tcptoudpmap(struct clientparam *param, int timeo){
struct pollfd fds[2];
PROXYSOCKADDRTYPE from;
SASIZETYPE size;
unsigned char *b = param->srvbuf;
int bufsize = param->srvbufsize;
int len = 0, res, n, hs = HS_MAYBE, hscan = 0;
for(;;){
fds[0].fd = param->clisock;
fds[0].events = POLLIN;
fds[0].revents = 0;
fds[1].fd = param->remsock;
fds[1].events = POLLIN;
fds[1].revents = 0;
res = param->srv->so._poll(param->sostate, fds, 2, timeo * 1000);
if(res < 0) return 481;
if(res == 0) return 92;
/* a reload: the session goes by the newest configuration's
rules, as a connection of sockmap() does */
if(param->version < runconf->version) {
if(param->srv->noforce || !reattachfunc) param->version = runconf->version;
else if((n = (*reattachfunc)(param, 1)) && n != 2) return n;
}
if(fds[0].revents){
n = (int)param->srv->so._recvfrom(param->sostate, param->clisock,
(char *)b + len, bufsize - len, 0, NULL, NULL);
if(n == 0) return 0;
if(n < 0){
if(errno == EAGAIN || errno == EINTR) n = 0;
else return 482;
}
len += n;
/* the handshake, until the first bytes tell which */
if(hs == HS_MAYBE){
/* 'G' of "GET " or 'u' of "udptunnel by ", or neither */
if(len >= 1 && b[0] != 'G' && b[0] != 'u') hs = HS_RAW;
else if(len >= 4){
if(!memcmp(b, "GET ", 4)) hs = HS_HTTP;
else if(!memcmp(b, "udpt", 4)) hs = HS_TUNNEL;
else hs = HS_RAW;
}
}
if(hs == HS_TUNNEL){
if(len < UDPTUN_MAGIC_LEN) continue;
memmove(b, b + UDPTUN_MAGIC_LEN, len - UDPTUN_MAGIC_LEN);
len -= UDPTUN_MAGIC_LEN;
hs = HS_RAW;
}
else if(hs == HS_HTTP){
n = tcptoudphdrend(b, hscan > 3? hscan - 3 : 0, len);
if(n < 0){
hscan = len;
if(len >= UDPTUN_MAXHDR || len >= bufsize) return 484;
continue;
}
/* the header block names the protocol or it is not
ProxyGuard asking */
if(!udptunprotoin(b, n)) return 484;
if(socksend(param, param->clisock, (unsigned char *)tcptoudp101,
(int)sizeof(tcptoudp101) - 1, timeo) != (int)sizeof(tcptoudp101) - 1)
return 487;
memmove(b, b + n + 4, len - n - 4);
len -= n + 4;
hs = HS_RAW;
}
if((res = tcptoudpframes(param, b, &len))) return res;
/* a datagram longer than the buffer is no client's */
if(len == bufsize) return 483;
}
if(fds[1].revents){
int k, next, off = 0;
size = sizeof(from);
n = (int)param->srv->so._recvfrom(param->sostate, param->remsock,
(char *)param->clibuf + UDPTUN_HDR, param->clibufsize - UDPTUN_HDR, 0,
(struct sockaddr *)&from, &size);
if(n < 0 && (errno == EAGAIN || errno == EINTR)) continue;
#ifdef _WIN32
/* an earlier datagram was refused (ICMP): not fatal */
if(n < 0 && WSAGetLastError() == WSAECONNRESET) continue;
#endif
if(n <= 0) return 486;
if(!param->udp_nhops){
/* none but the target itself */
if(!SAISNULL(&param->sinsr) && *SAPORT(&param->sinsr) &&
(SAADDRLEN(&from) != SAADDRLEN(&param->sinsr) ||
memcmp(SAADDR(&from), SAADDR(&param->sinsr), SAADDRLEN(&from)) ||
memcmp(SAPORT(&from), SAPORT(&param->sinsr), 2)))
return 0;
}
else for(k = 0; k < param->udp_nhops; k++){
if((next = socks5_udp_skip_hdr(param->clibuf + UDPTUN_HDR + off, n - off)) < 0) break;
off += next;
}
if(off) n -= off;
param->statssrv64 += n;
param->nreads++;
if(param->bandlimfunc && (*param->bandlimfunc)(param, n, 0)) continue;
udptunput(param->clibuf, n);
if(socksend(param, param->clisock, param->clibuf, n + UDPTUN_HDR, timeo) != n + UDPTUN_HDR)
return 487;
}
}
}
void *tcptoudpchild(struct clientparam* param) {
int res;
if(!*param->hostname){
if(parsehostname((char *)param->srv->target, param, ntohs(param->srv->targetport))) RETURN(100);
}
param->sinsl = *SAFAMILY(&param->req) == AF_INET6? param->srv->extsa6 : param->srv->extsa;
*SAPORT(&param->sinsl) = 0;
param->sinsr = param->req;
param->operation = UDPASSOC;
res = (*param->srv->authfunc)(param);
if(res) {RETURN(res);}
if (param->npredatfilters){
int action;
action = handlepredatflt(param);
if(action == HANDLED) RETURN(0);
if(action != PASS) RETURN(19);
}
if(param->redirectfunc){
return (void *)param->redirectfunc;
}
if((res = udpbind(param))) RETURN(res);
RETURN(tcptoudpmap(param, param->srv->timeouts[CONNECTION_L]));
CLEANRET:
dolog(param, *param->hostname? param->hostname : NULL);
return (NULL);
}
#ifdef WITHMAIN
struct proxydef childdef = {
tcptoudpchild,
0,
0,
S_TCPTOUDP,
" the handshake a client speaks, udptunnel's or ProxyGuard's, is answered\n",
NULL
};
#include "proxymain.c"
#endif

307
src/udptotcp.c Normal file
View File

@ -0,0 +1,307 @@
/*
3APA3A simplest proxy server
(c) 2002-2026 by Vladimir Dubrovin <vlad@3proxy.org>
please read License Agreement
*/
#include "proxy.h"
#include "udptunnel.h"
#ifndef PORTMAP
#define PORTMAP
#endif
#ifndef UDP
#define UDP
#endif
/* udptotcp: datagrams in, from any number of clients, each client a
session of the service's own (see udploop() in proxymain.c) with a
TCP connection of its own to the target. On the connection the
datagrams carry their length in front of them (udptunnel.h); -m
sends udptunnel's handshake to the target first, -m2 ProxyGuard's
HTTP Upgrade, -m0 (the default) none. What comes back on the
connection is answered to the client the session is. A parent the
ACL gives is asked for a TCP connection, unlike udppm's UDP relay. */
/* the handshake -m asks for: none, udptunnel's, ProxyGuard's */
enum { U2T_NONE, U2T_TUNNEL, U2T_HTTP };
/* what the session's connection is up to */
enum { U2T_CONNECTING, U2T_ASKING, U2T_RUN };
struct u2t {
int state;
int mode; /* the handshake of U2T_* */
unsigned char *out; /* framed datagrams waiting to be written */
int outlen, outoff, outcap;
unsigned char in[UDPTUN_HDR + UDPBUFSIZE]; /* a datagram being read */
int inlen;
};
/* udptunnel's handshake, "udptunnel by md." of the rfc1036 udptunnel
followed by zeroes and a counting sequence */
static const unsigned char u2tmagic[UDPTUN_MAGIC_LEN] =
"udptunnel by md."
"\0\0\0\001\003\006\020\025\041\050\066\105\125\146\170\221";
/* bytes at the end of the queue, or -1 if the connection is gone */
static int u2tflush(struct clientparam *param, struct u2t *d){
while(d->outoff < d->outlen){
int s = (int)param->srv->so._send(param->sostate, param->remsock,
(char *)d->out + d->outoff, d->outlen - d->outoff, 0);
if(s > 0){ d->outoff += s; continue; }
if(s < 0 && (errno == EAGAIN || errno == EINTR)) return 0;
return -1;
}
return 1;
}
static void u2tcompact(struct u2t *d){
if(d->outoff){
memmove(d->out, d->out + d->outoff, d->outlen - d->outoff);
d->outlen -= d->outoff;
d->outoff = 0;
}
}
/* bytes without a prefix on the queue, a handshake's; -1 without room */
static int u2tput(struct u2t *d, const unsigned char *data, int n){
unsigned char *o;
if(d->outlen + n > d->outcap){
int want = d->outcap? 2 * d->outcap : UDPBUFSIZE;
while(want < d->outlen + n){
int w = 2 * want;
if(w <= want) return -1;
want = w;
}
if(!(o = realloc(d->out, want))) return -1;
d->out = o;
d->outcap = want;
}
memcpy(d->out + d->outlen, data, n);
d->outlen += n;
return 0;
}
/* a datagram, with its length in front of it, on the queue */
static int u2tframe(struct u2t *d, const unsigned char *data, int n){
unsigned char h[UDPTUN_HDR];
udptunput(h, n);
if(u2tput(d, h, UDPTUN_HDR)) return -1;
return u2tput(d, data, n);
}
/* Where the end of a header block was looked for up to */
static int u2thdrend(unsigned char *b, int from, int len){
int i;
for(i = from; i + 3 < len; i++)
if(b[i] == '\r' && b[i+1] == '\n' && b[i+2] == '\r' && b[i+3] == '\n') return i;
return -1;
}
static int udptotcpfirst(struct udploop *u, struct udpsession *ss, unsigned char *buf, int len){
struct clientparam *param = ss->param;
struct u2t *d;
int res;
SOCKET s;
if(parsehostname((char *)param->srv->target, param, ntohs(param->srv->targetport))) return (param->res = 100);
param->sinsl = *SAFAMILY(&param->req) == AF_INET6? param->srv->extsa6 : param->srv->extsa;
*SAPORT(&param->sinsl) = 0;
param->sinsr = param->req;
/* a parent, if the ACL gives one, is asked for a TCP connection */
param->operation = CONNECT;
if((res = (*param->srv->authfunc)(param))) return (param->res = res);
if(!(d = calloc(1, sizeof(*d)))) return (param->res = 21);
ss->data = d;
d->mode = param->srv->s_option == U2T_TUNNEL || param->srv->s_option == U2T_HTTP?
param->srv->s_option : U2T_NONE;
/* the chain walk, if a parent gave a connection, has connected it;
otherwise the session's own is connected as the loop sees it
writable (dnspr asks a name server over TCP the same way) */
if(param->remsock == INVALID_SOCKET){
if((s = nssocket(param->srv, &param->srv->so, param->sostate, NS_OUT, SASOCK(&param->sinsr), SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET){
free(d);
ss->data = NULL;
return (param->res = 11);
}
param->remsock = s;
if((*param->srv->so._connect)(param->sostate, s, (struct sockaddr *)&param->sinsr, SASIZE(&param->sinsr))
#ifdef _WIN32
&& WSAGetLastError() != WSAEWOULDBLOCK
#else
&& errno != EINPROGRESS
#endif
){
free(d);
ss->data = NULL;
return (param->res = 12);
}
}
d->state = U2T_CONNECTING;
ss->events = POLLOUT;
ss->deadline = u->now + (uint64_t)param->srv->timeouts[CONNECT_TO] * 1000;
/* the handshake the mode asks for, sent once the connection is
made, the first datagram after it */
if(d->mode == U2T_TUNNEL){
if(u2tput(d, u2tmagic, UDPTUN_MAGIC_LEN)) return (param->res = 21);
}
else if(d->mode == U2T_HTTP){
char req[HOSTNAMELEN + 128];
res = sprintf(req, "GET / HTTP/1.1\r\nHost: %.256s\r\nUser-Agent: 3proxy\r\nConnection: Upgrade\r\nUpgrade: " UDPTUN_PROTO "\r\n\r\n",
(char *)param->srv->target);
if(u2tput(d, (unsigned char *)req, res)) return (param->res = 21);
}
if(!param->bandlimfunc || !(*param->bandlimfunc)(param, 0, len)){
if(u2tframe(d, buf, len)) return (param->res = 21);
param->statscli64 += len;
param->nwrites++;
}
return 0;
}
static int udptotcpclient(struct udploop *u, struct udpsession *ss, unsigned char *buf, int len){
struct clientparam *param = ss->param;
struct u2t *d = ss->data;
if(param->bandlimfunc && (*param->bandlimfunc)(param, 0, len)) return 0;
/* the connection is not taking what it is given */
if(d->outlen - d->outoff > 65536) return (param->res = 487);
if(u2tframe(d, buf, len)) return (param->res = 21);
param->statscli64 += len;
param->nwrites++;
if(d->state == U2T_RUN){
if(u2tflush(param, d) < 0) return (param->res = 487);
u2tcompact(d);
ss->events = d->outoff < d->outlen? POLLIN | POLLOUT : POLLIN;
}
return 0;
}
static int udptotcpserver(struct udploop *u, struct udpsession *ss){
struct clientparam *param = ss->param;
struct u2t *d = ss->data;
int len, n, off, err;
SASIZETYPE elen;
if(d->state == U2T_CONNECTING){
err = 0;
elen = sizeof(err);
if(param->srv->so._getsockopt(param->sostate, param->remsock, SOL_SOCKET, SO_ERROR, (char *)&err, &elen) || err)
return (param->res = 60);
d->state = d->mode == U2T_HTTP? U2T_ASKING : U2T_RUN;
/* the handshake and the datagrams after it */
if(u2tflush(param, d) < 0) return (param->res = 487);
u2tcompact(d);
if(d->state == U2T_RUN){
ss->idle = param->srv->timeouts[STRING_L];
ss->deadline = u->now + (uint64_t)ss->idle * 1000;
ss->events = d->outoff < d->outlen? POLLIN | POLLOUT : POLLIN;
}
else ss->events = POLLIN;
return 0;
}
if(d->state == U2T_ASKING){
/* ProxyGuard's answer: a header block, then the framing */
int i;
len = (int)param->srv->so._recv(param->sostate, param->remsock,
(char *)d->in + d->inlen, (int)sizeof(d->in) - d->inlen, 0);
if(len < 0 && (errno == EAGAIN || errno == EINTR)) return 0;
if(len <= 0) return (param->res = 487);
d->inlen += len;
i = u2thdrend(d->in, (d->inlen - len > 3)? d->inlen - len - 3 : 0, d->inlen);
if(i < 0){
if(d->inlen >= UDPTUN_MAXHDR || d->inlen >= (int)sizeof(d->in)) return (param->res = 63);
return 0;
}
/* a 101 answer naming the protocol */
n = 0;
while(n < i && d->in[n] != ' ') n++;
if(n + 4 > i || memcmp(d->in + n + 1, "101", 3) || !udptunprotoin(d->in, i))
return (param->res = 63);
d->inlen -= i + 4;
if(d->inlen > 0) memmove(d->in, d->in + i + 4, d->inlen);
d->state = U2T_RUN;
ss->idle = param->srv->timeouts[STRING_L];
ss->deadline = u->now + (uint64_t)ss->idle * 1000;
ss->events = d->outoff < d->outlen? POLLIN | POLLOUT : POLLIN;
return 0;
}
/* U2T_RUN: first what waits, then what has come */
if(d->outoff < d->outlen){
if(u2tflush(param, d) < 0) return (param->res = 487);
u2tcompact(d);
ss->events = d->outoff < d->outlen? POLLIN | POLLOUT : POLLIN;
}
len = (int)param->srv->so._recv(param->sostate, param->remsock,
(char *)d->in + d->inlen, (int)sizeof(d->in) - d->inlen, 0);
if(len < 0 && (errno == EAGAIN || errno == EINTR)) return 0;
if(len == 0) return 1; /* the target closed: over */
if(len < 0) return (param->res = 486);
d->inlen += len;
off = 0;
while(d->inlen - off >= UDPTUN_HDR){
n = udptunget(d->in + off);
if(n > UDPBUFSIZE) return (param->res = 483);
if(d->inlen - off < UDPTUN_HDR + n) break;
param->statssrv64 += n;
param->nreads++;
if(!param->bandlimfunc || !(*param->bandlimfunc)(param, n, 0))
udpanswer(u, param, d->in + off + UDPTUN_HDR, n);
off += UDPTUN_HDR + n;
}
if(off){
memmove(d->in, d->in + off, d->inlen - off);
d->inlen -= off;
}
/* half a datagram, and no room for the rest of it */
if(d->inlen == (int)sizeof(d->in)) return (param->res = 483);
return 0;
}
static void udptotcpend(struct udploop *u, struct udpsession *ss){
struct u2t *d = ss->data;
if(d){
free(d->out);
free(d);
ss->data = NULL;
}
dolog(ss->param, NULL);
}
const struct udpservice udptotcpservice = {
1,
udptotcpfirst,
udptotcpclient,
udptotcpserver,
udptotcpend,
92,
NULL
};
#ifdef WITHMAIN
struct proxydef childdef = {
NULL,
0,
1,
S_UDPTOTCP,
" -m - udptunnel handshake with the target, -m2 - ProxyGuard's, -m0 - none\n",
&udptotcpservice
};
#include "proxymain.c"
#endif

69
src/udptunnel.h Normal file
View File

@ -0,0 +1,69 @@
/*
3APA3A simplest proxy server
(c) 2002-2026 by Vladimir Dubrovin <vlad@3proxy.org>
please read License Agreement
*/
/* What the tunnels between TCP and UDP speak, tcptoudp and udptotcp:
datagrams on a TCP connection, each with its length, in network
order, in front of it. udptunnel (the rfc1036 one) and ProxyGuard's
UoTLV/1 (eduVPN) put datagrams on a connection the same way; what
they differ in is what comes first:
- nothing: the framing alone;
- udptunnel's handshake: 32 bytes beginning "udptunnel by ", which
the client sends and no answer follows;
- ProxyGuard's: an HTTP Upgrade, the client asking with "GET",
"Connection: Upgrade" and "Upgrade: UoTLV/1", the server answering
"101 Switching Protocols" before the framing begins.
Which of these a connection speaks is told by its first bytes, "GET"
or "udptunnel by ", so tcptoudp takes any of them from a client;
udptotcp sends the one it was configured to (-m: udptunnel's, -m2:
ProxyGuard's, -m0: none). */
#ifndef _UDPTUNNEL_H
#define _UDPTUNNEL_H
#include <ctype.h>
/* the length of the prefix a datagram carries on the connection */
#define UDPTUN_HDR 2
/* udptunnel's handshake: "udptunnel by md." of the rfc1036 udptunnel,
followed by zeroes and a counting sequence; any 32 bytes beginning
with the prefix are taken, the original Columbia one included */
#define UDPTUN_MAGIC_LEN 32
#define UDPTUN_PREFIX "udptunnel by "
#define UDPTUN_PREFIX_LEN 13
/* ProxyGuard's protocol, as the Upgrade: header names it */
#define UDPTUN_PROTO "UoTLV/1"
/* a header of a handshake is not longer */
#define UDPTUN_MAXHDR 2048
/* the length a datagram's prefix tells */
static int udptunget(const unsigned char *p){
return (p[0] << 8) | p[1];
}
/* the prefix of a datagram of this length */
static void udptunput(unsigned char *p, int n){
p[0] = (unsigned char)(n >> 8);
p[1] = (unsigned char)n;
}
/* whether the bytes of a handshake name the protocol, whatever the
case of the letters */
static int udptunprotoin(const unsigned char *b, int len){
int i, j;
for(i = 0; i + 7 <= len; i++){
for(j = 0; j < 7; j++)
if(tolower(b[i + j]) != tolower((unsigned char)UDPTUN_PROTO[j])) break;
if(j == 7) return 1;
}
return 0;
}
#endif

106
tests/cases/tunnels.py Normal file
View File

@ -0,0 +1,106 @@
"""The UDP<->TCP tunnels: udptotcp takes datagrams to a TCP connection,
tcptoudp takes a TCP connection to datagrams, the datagrams on the
connection carrying their length, as udptunnel and ProxyGuard put them."""
import socket
import struct
import threading
UDP_MAGIC = b"udptunnel by md.\0\0\0\x01\x03\x06\x10\x15\x21\x28\x36\x45\x55\x66\x78\x91"
PG_GET = (b"GET / HTTP/1.1\r\nHost: 127.0.0.1\r\nUser-Agent: test\r\n"
b"Connection: Upgrade\r\nUpgrade: UoTLV/1\r\n\r\n")
def frame(payload):
return struct.pack(">H", len(payload)) + payload
def read_frames(sock, state, count, timeout=10.0):
"""count length-prefixed datagrams from sock; state[0] is the bytes
already read and not yet used."""
out = []
sock.settimeout(timeout)
while len(out) < count:
while len(state[0]) >= 2:
need = 2 + struct.unpack(">H", state[0][:2])[0]
if len(state[0]) < need:
break
out.append(state[0][2:need])
state[0] = state[0][need:]
if len(out) >= count:
return out
data = sock.recv(65536)
if not data:
raise EOFError("the tunnel closed")
state[0] += data
return out
def run(t):
echo = t.udp_echo()
tunnel = t.free_port() # udptotcp listens here
tunnel_hs = t.free_port() # the same, udptunnel's handshake
server = t.free_port() # tcptoudp listens here
plain = t.free_port() # tcptoudp for the handshake clients
t.start("tunnels", f"""
log
auth iponly
allow *
udptotcp {tunnel} 127.0.0.1 {server}
udptotcp -m {tunnel_hs} 127.0.0.1 {server}
flush
auth iponly
allow *
tcptoudp {server} 127.0.0.1 {echo}
tcptoudp {plain} 127.0.0.1 {echo}
""", ports=[server, plain])
# a UDP service answers only once it is asked, so ask until it does
if t.wait_udp(tunnel):
t.ok("the UDP side of the tunnel is up")
else:
t.fail("the UDP side of the tunnel is up")
# --- a datagram each way, through both services ---------------
t.eq(b"echo:hello", t.udp_exchange(tunnel, b"hello"),
"a datagram reaches the target through the tunnel")
t.eq(b"echo:again", t.udp_exchange(tunnel, b"again"),
"a second datagram uses the same session")
big = b"x" * 4000
t.eq(b"echo:" + big, t.udp_exchange(tunnel, big),
"a larger datagram survives the round trip")
# --- udptunnel's handshake, accepted by tcptoudp ---------------
if t.wait_udp(tunnel_hs):
t.ok("the UDP side of the udptunnel-handshake tunnel is up")
t.eq(b"echo:with handshake", t.udp_exchange(tunnel_hs, b"with handshake"),
"a datagram with udptunnel's handshake before it is answered")
# --- a client speaking udptunnel, to tcptoudp ------------------
s = socket.create_connection(("127.0.0.1", plain), timeout=10)
s.sendall(UDP_MAGIC + frame(b"udptunnel asks"))
t.eq([b"echo:udptunnel asks"], read_frames(s, [b""], 1),
"a udptunnel client is answered")
s.close()
# --- a client speaking ProxyGuard, to tcptoudp -----------------
s = socket.create_connection(("127.0.0.1", plain), timeout=10)
s.sendall(PG_GET)
head = b""
while b"\r\n\r\n" not in head:
head += s.recv(4096)
t.eq(True, head.startswith(b"HTTP/1.1 101"),
"ProxyGuard's client is told the protocols are switched")
t.contains(head.decode("latin1"), "UoTLV/1", "the answer names the protocol")
s.sendall(frame(b"proxyguard asks"))
t.eq([b"echo:proxyguard asks"], read_frames(s, [b""], 1),
"ProxyGuard's datagram is answered")
s.close()
# --- a client that frames, two datagrams in one write ----------
s = socket.create_connection(("127.0.0.1", plain), timeout=10)
s.sendall(frame(b"first") + frame(b"second"))
t.eq([b"echo:first", b"echo:second"], read_frames(s, [b""], 2),
"two datagrams in one write are both answered")
s.close()