diff --git a/.github/workflows/update-wiki.yml b/.github/workflows/update-wiki.yml index 5f2db13..079361e 100644 --- a/.github/workflows/update-wiki.yml +++ b/.github/workflows/update-wiki.yml @@ -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 diff --git a/.gitignore b/.gitignore index 5625b9a..ae9b9db 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/CMakeLists.txt b/CMakeLists.txt index 861d19a..ed91dd8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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} $ $ + $ $ $ $ @@ -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 $) 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} diff --git a/Makefile.FreeBSD b/Makefile.FreeBSD index abbb24b..20b46db 100644 --- a/Makefile.FreeBSD +++ b/Makefile.FreeBSD @@ -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 diff --git a/Makefile.Linux b/Makefile.Linux index ba7cb12..5468bb5 100644 --- a/Makefile.Linux +++ b/Makefile.Linux @@ -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 diff --git a/man/3proxy.8 b/man/3proxy.8 index 97d2bc9..d6d3e41 100644 --- a/man/3proxy.8 +++ b/man/3proxy.8 @@ -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/ diff --git a/man/3proxy.cfg.5 b/man/3proxy.cfg.5 index 2ad48e2..18ae995 100644 --- a/man/3proxy.cfg.5 +++ b/man/3proxy.cfg.5 @@ -79,6 +79,14 @@ Recursion is not allowed. .B udppm [options] \fI\fR \fI\fR \fI\fR +.br +.B tcptoudp +[options] +\fI\fR \fI\fR \fI\fR +.br +.B udptotcp +[options] +\fI\fR \fI\fR \fI\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 diff --git a/man/tcptoudp.8 b/man/tcptoudp.8 new file mode 100644 index 0000000..700b351 --- /dev/null +++ b/man/tcptoudp.8 @@ -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 diff --git a/man/udptotcp.8 b/man/udptotcp.8 new file mode 100644 index 0000000..57f3276 --- /dev/null +++ b/man/udptotcp.8 @@ -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 diff --git a/scripts/rh/3proxy.spec b/scripts/rh/3proxy.spec index e675567..819edc3 100644 --- a/scripts/rh/3proxy.spec +++ b/scripts/rh/3proxy.spec @@ -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 diff --git a/src/3proxy.c b/src/3proxy.c index 1af109f..2fa80ac 100644 --- a/src/3proxy.c +++ b/src/3proxy.c @@ -29,7 +29,7 @@ void httpsrv_init(void); #endif #ifndef DEFAULTCONFIG -#define DEFAULTCONFIG conf->stringtable[25] +#define DEFAULTCONFIG conf->stringtable[27] #endif FILE * confopen(); diff --git a/src/Makefile.inc b/src/Makefile.inc index ace05c8..c748919 100644 --- a/src/Makefile.inc +++ b/src/Makefile.inc @@ -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) diff --git a/src/conf.c b/src/conf.c index 2ee632b..e39c698 100644 --- a/src/conf.c +++ b/src/conf.c @@ -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 diff --git a/src/mainconf.c b/src/mainconf.c index acb64d4..03fb82b 100644 --- a/src/mainconf.c +++ b/src/mainconf.c @@ -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 diff --git a/src/proxy.h b/src/proxy.h index 269397b..1d5f911 100644 --- a/src/proxy.h +++ b/src/proxy.h @@ -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); diff --git a/src/stringtable.c b/src/stringtable.c index c0abe3d..631809a 100644 --- a/src/stringtable.c +++ b/src/stringtable.c @@ -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, diff --git a/src/structures.h b/src/structures.h index a7744ad..eadce75 100644 --- a/src/structures.h +++ b/src/structures.h @@ -240,6 +240,8 @@ typedef enum { S_TLSPR, S_IMAPP, S_HTTPSRV, + S_TCPTOUDP, + S_UDPTOTCP, S_ZOMBIE }PROXYSERVICE; diff --git a/src/tcptoudp.c b/src/tcptoudp.c new file mode 100644 index 0000000..b1f0de0 --- /dev/null +++ b/src/tcptoudp.c @@ -0,0 +1,230 @@ +/* + 3APA3A simplest proxy server + (c) 2002-2026 by Vladimir Dubrovin + + 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, ¶m->udp_relay[i-1]); + off += socks5_udp_build_hdr(param->clibuf + off, ¶m->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 *)¶m->sinsr, SASIZE(¶m->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(¶m->sinsr) && *SAPORT(¶m->sinsr) && + (SAADDRLEN(&from) != SAADDRLEN(¶m->sinsr) || + memcmp(SAADDR(&from), SAADDR(¶m->sinsr), SAADDRLEN(&from)) || + memcmp(SAPORT(&from), SAPORT(¶m->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(¶m->req) == AF_INET6? param->srv->extsa6 : param->srv->extsa; + *SAPORT(¶m->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 diff --git a/src/udptotcp.c b/src/udptotcp.c new file mode 100644 index 0000000..a8d91e1 --- /dev/null +++ b/src/udptotcp.c @@ -0,0 +1,307 @@ +/* + 3APA3A simplest proxy server + (c) 2002-2026 by Vladimir Dubrovin + + 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(¶m->req) == AF_INET6? param->srv->extsa6 : param->srv->extsa; + *SAPORT(¶m->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, ¶m->srv->so, param->sostate, NS_OUT, SASOCK(¶m->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 *)¶m->sinsr, SASIZE(¶m->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 diff --git a/src/udptunnel.h b/src/udptunnel.h new file mode 100644 index 0000000..47edf2a --- /dev/null +++ b/src/udptunnel.h @@ -0,0 +1,69 @@ +/* + 3APA3A simplest proxy server + (c) 2002-2026 by Vladimir Dubrovin + + 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 + +/* 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 diff --git a/tests/cases/tunnels.py b/tests/cases/tunnels.py new file mode 100644 index 0000000..8f9478d --- /dev/null +++ b/tests/cases/tunnels.py @@ -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()