diff --git a/.github/workflows/freebsd_build.yml b/.github/workflows/freebsd_build.yml new file mode 100644 index 00000000..fe7c4c6a --- /dev/null +++ b/.github/workflows/freebsd_build.yml @@ -0,0 +1,22 @@ +name: freebsd_build + +on: [push] + +jobs: + build: + runs-on: ubuntu-22.04 + name: A job to run test FreeBSD + # env: + steps: + - uses: actions/checkout@v2 + - name: Build in FreeBSD + id: test + uses: vmactions/freebsd-vm@v1 + with: + # envs: 'MYTOKEN MYTOKEN2' + usesh: true + prepare: pkg install -y curl libevent gmake openssl + run: | + pwd + freebsd-version + gmake diff --git a/.github/workflows/linux_build.yml b/.github/workflows/linux_build.yml new file mode 100644 index 00000000..929b6cb0 --- /dev/null +++ b/.github/workflows/linux_build.yml @@ -0,0 +1,15 @@ +name: linux_build + +on: [push] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: install depends + run: | + sudo apt update + sudo apt install -y libevent-dev libssl-dev + - name: Build + run: make diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..c89c98da --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,25 @@ +name: tagged-release + +on: + push: + tags: + - "release-*" + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: install depends + run: | + sudo apt update + sudo apt install -y libevent-dev libmbedtls-dev zlib1g-dev + - name: Build + run: | + mkdir release + make USE_CRYPTO_MBEDTLS=1 ENABLE_STATIC=1 + mv redsocks2 release/redsocks2-`arch`-${{github.ref_name}} + - name: Release + uses: softprops/action-gh-release@v1 + with: + files: release/* diff --git a/.gitignore b/.gitignore index 5930ef53..b78af580 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ *.o config.h tags -redsocks +redsocks2 +gen/ .depend +xnu/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..261eeb9e --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Makefile b/Makefile index dd320777..9116b170 100644 --- a/Makefile +++ b/Makefile @@ -1,13 +1,71 @@ -OBJS := parser.o main.o redsocks.o log.o http-connect.o socks4.o socks5.o http-relay.o base.o base64.o md5.o http-auth.o utils.o redudp.o dnstc.o gen/version.o +ifdef DISABLE_SHADOWSOCKS +OBJS := parser.o main.o redsocks.o log.o direct.o ipcache.o autoproxy.o http-connect.o \ + socks4.o socks5.o http-relay.o base.o base64.o md5.o http-auth.o utils.o redudp.o socks5-udp.o \ + tcpdns.o gen/version.o +CFLAGS +=-fPIC -O3 -DDISABLE_SHADOWSOCKS +FEATURES += DISABLE_SHADOWSOCKS +else +OBJS := parser.o main.o redsocks.o log.o direct.o ipcache.o autoproxy.o encrypt.o shadowsocks.o http-connect.o \ + socks4.o socks5.o http-relay.o base.o base64.o md5.o http-auth.o utils.o redudp.o socks5-udp.o shadowsocks-udp.o \ + tcpdns.o gen/version.o +CFLAGS +=-fPIC -O3 +endif SRCS := $(OBJS:.o=.c) CONF := config.h DEPS := .depend -OUT := redsocks -VERSION := 0.4 +OUT := redsocks2 +VERSION := 0.71 +OS := $(shell uname) LIBS := -levent -CFLAGS += -g -O2 -override CFLAGS += -std=c99 -D_XOPEN_SOURCE=600 -D_BSD_SOURCE -D_DEFAULT_SOURCE -Wall +override CFLAGS += -D_BSD_SOURCE -D_DEFAULT_SOURCE -Wall +ifeq ($(OS), Linux) +override CFLAGS += -std=c99 -D_XOPEN_SOURCE=600 +endif +ifeq ($(OS), FreeBSD) +override CFLAGS +=-I/usr/local/include -L/usr/local//lib +endif +ifeq ($(OS), OpenBSD) +override CFLAGS +=-I/usr/local/include -L/usr/local//lib #same as FreeBSD +endif +ifeq ($(OS), Darwin) +override CFLAGS +=-I/usr/local/opt/openssl/include -L/usr/local/opt/openssl/lib +SHELL := /bin/bash +OSX_VERSION := $(shell sw_vers -productVersion | cut -d '.' -f 1,2) +OSX_ROOT_PATH := xnu +OSX_HEADERS_PATH := $(OSX_ROOT_PATH)/$(OSX_VERSION) +OSX_HEADERS := $(OSX_HEADERS_PATH)/net/pfvar.h $(OSX_HEADERS_PATH)/net/radix.h $(OSX_HEADERS_PATH)/libkern/tree.h +override CFLAGS +=-I$(OSX_HEADERS_PATH) +endif + + +#LDFLAGS += -fwhole-program +ifdef USE_CRYPTO_MBEDTLS +override LIBS += -lmbedtls -lmbedx509 -lmbedcrypto +override CFLAGS += -DUSE_CRYPTO_MBEDTLS +$(info Compile with mbedTLS.) +CRYPTO := mbedTLS +else +$(info Compile with OpenSSL by default. To compile with mbedTLS, run 'make USE_CRYPTO_MBEDTLS=true' instead.) +CRYPTO := OpenSSL +ifdef ENABLE_HTTPS_PROXY +override OBJS += https-connect.o +override LIBS += -levent_openssl +override CFLAGS += -DENABLE_HTTPS_PROXY +override FEATURES += ENABLE_HTTPS_PROXY +$(info Compile with HTTPS proxy enabled.) +endif +override LIBS += -lssl -lcrypto +ifneq ($(OS), OpenBSD) +override LIBS += -ldl +endif +override CFLAGS += -DUSE_CRYPTO_OPENSSL +endif +ifdef ENABLE_STATIC +override LIBS += $(shell nm -u $(shell $(CC) -print-file-name=libcrypto.a) 2>/dev/null | grep -q 'deflate\|inflate\|zlibVersion' && echo -lz) +override LDFLAGS += -Wl,-static -static -static-libgcc -s +override FEATURES += STATIC_COMPILE +endif all: $(OUT) @@ -17,13 +75,22 @@ tags: *.c *.h ctags -R $(CONF): - @case `uname` in \ + @case $(OS) in \ Linux*) \ echo "#define USE_IPTABLES" >$(CONF) \ ;; \ + FreeBSD) \ + echo "#define USE_PF" >$(CONF) \ + ;; \ OpenBSD) \ echo "#define USE_PF" >$(CONF) \ ;; \ + NetBSD) \ + echo "#define USE_PF" >$(CONF) \ + ;; \ + Darwin) \ + echo -e "#define USE_PF\n#define _APPLE_" >$(CONF) \ + ;; \ *) \ echo "Unknown system, only generic firewall code is compiled" 1>&2; \ echo "/* Unknown system, only generic firewall code is compiled */" >$(CONF) \ @@ -32,17 +99,21 @@ $(CONF): # Dependency on .git is useful to rebuild `version.c' after commit, but it breaks non-git builds. gen/version.c: *.c *.h gen/.build - rm -f $@.tmp + $(RM) -f $@.tmp echo '/* this file is auto-generated during build */' > $@.tmp echo '#include "../version.h"' >> $@.tmp echo 'const char* redsocks_version = ' >> $@.tmp if [ -d .git ]; then \ - echo '"redsocks.git/'`git describe --tags`'"'; \ + echo '"redsocks.git/'`git describe --tags`' $(CRYPTO)"'; \ if [ `git status --porcelain | grep -v -c '^??'` != 0 ]; then \ echo '"-unclean"'; \ - fi \ + fi; \ + echo '"\\n"'; \ + echo '"Features: $(FEATURES)"'; \ else \ - echo '"redsocks/$(VERSION)"'; \ + echo '"redsocks/$(VERSION) $(CRYPTO)"'; \ + echo '"\\n"'; \ + echo '"Features: $(FEATURES)"'; \ fi >> $@.tmp echo ';' >> $@.tmp mv -f $@.tmp $@ @@ -53,8 +124,17 @@ gen/.build: base.c: $(CONF) -$(DEPS): $(SRCS) - gcc -MM $(SRCS) 2>/dev/null >$(DEPS) || \ +ifeq ($(OS), Darwin) +$(OSX_HEADERS_PATH)/net/pfvar.h: + mkdir -p $(OSX_HEADERS_PATH)/net && curl -o $(OSX_HEADERS_PATH)/net/pfvar.h https://raw.githubusercontent.com/apple/darwin-xnu/master/bsd/net/pfvar.h +$(OSX_HEADERS_PATH)/net/radix.h: + mkdir -p $(OSX_HEADERS_PATH)/net && curl -o $(OSX_HEADERS_PATH)/net/radix.h https://raw.githubusercontent.com/apple/darwin-xnu/master/bsd/net/radix.h +$(OSX_HEADERS_PATH)/libkern/tree.h: + mkdir -p $(OSX_HEADERS_PATH)/libkern && curl -o $(OSX_HEADERS_PATH)/libkern/tree.h https://raw.githubusercontent.com/apple/darwin-xnu/master/libkern/libkern/tree.h +endif + +$(DEPS): $(OSX_HEADERS) $(SRCS) + $(CC) -MM $(CFLAGS) $(SRCS) 2>/dev/null >$(DEPS) || \ ( \ for I in $(wildcard *.h); do \ export $${I//[-.]/_}_DEPS="`sed '/^\#[ \t]*include \?"\(.*\)".*/!d;s//\1/' $$I`"; \ @@ -82,8 +162,10 @@ $(OUT): $(OBJS) $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) $(LIBS) clean: - $(RM) $(OUT) $(CONF) $(OBJS) + $(RM) $(CONF) $(OBJS) distclean: clean + $(RM) $(OUT) $(RM) tags $(DEPS) $(RM) -r gen + $(RM) -r $(OSX_ROOT_PATH) diff --git a/README b/README index 237c88f3..fc822b04 100644 --- a/README +++ b/README @@ -1,3 +1,7 @@ +This is a enhanced version of original redsocks. + +The content below is from original redsocks project. +--------------------------------------------------------------------- This tool allows you to redirect any TCP connection to SOCKS or HTTPS proxy using your firewall, so redirection is system-wide. diff --git a/README.html b/README.html deleted file mode 100644 index 974d9074..00000000 --- a/README.html +++ /dev/null @@ -1,237 +0,0 @@ - - - - - -redsocks - transparent socks redirector - - - -

redsocks - transparent socks redirector

- - -
- -

This tool allows you to redirect any TCP connection to SOCKS or HTTPS -proxy using your firewall, so redirection is system-wide.

- -

Why is that useful? I can suggest following reasons:

- - -

Linux/iptables, OpenBSD/pf and FreeBSD/ipfw are supported. -Linux/iptables is well-tested, other implementations may have bugs, -your bugreports are welcome.

- -

Transocks is alike project but it has noticable performance penality.

- -

Transsocks_ev is alike project too, but it has no HTTPS-proxy support -and does not support authentication.

- -

Several Andoird apps also use redsocks under-the-hood: ProxyDroid (@AndroidMarket) and -sshtunnel (@AndroidMarket). And that's over 100'000 downloads! Wow!

- -

Another related issue is DNS over TCP. Redsocks includes `dnstc' that is fake -and really dumb DNS server that returns "truncated answer" to every query via -UDP. RFC-compliant resolver should repeat same query via TCP in this case - so -the request can be redirected using usual redsocks facilities.

- -

Known compliant resolvers are:

- -

Known non-compliant resolvers are:

- - -

On the other hand, DNS via TCP using bind9 may be painfully slow. If your bind9 -setup is really slow, you have at least two options: pdnsd caching server -can run in TCP-only mode, ttdnsd (git repo) has no cache but can be useful for same -purpose.

- -

Features

- -

Redirect any TCP connection to SOCKS4, SOCKS5 or HTTPS (HTTP/CONNECT) -proxy server.

- -

Login/password authentication is supported for SOCKS5/HTTPS connections. -SOCKS4 supports only username, password is ignored. for HTTPS, currently -only Basic and Digest scheme is supported.

- -

Redirect UDP packets via SOCKS5 proxy server. NB: UDP still goes via UDP, so -you can't relay UDP via OpenSSH.

- -

Sends "truncated reply" as an answer to UDP DNS queries.

- -

Redirect any HTTP connection to proxy that does not support transparent -proxying (e.g. old SQUID had broken `acl myport' for such connections).

- - -

License

- -

All source code is licensed under Apache 2.0 license.

-

You can get a copy at http://www.apache.org/licenses/LICENSE-2.0.html

- - -

Packages

- - - -

Compilation

- -

libevent is required.

- -

gcc is only supported compiler right now, other compilers can be used -but may require some code changes.

- -

Compilation is as easy as running `make', there is no `./configure' magic.

- -

GNU Make works, other implementations of make were not tested.

- - -

Running

- -

Program has following command-line options:
--c sets proper path to config file ("./redsocks.conf" is default one)
--t tests config file syntax
--p set a file to write the getpid() into

- -

Following signals are understood:
-SIGUSR1 dumps list of connected clients to log
-SIGTERM and SIGINT terminates daemon, all active connections are closed

- -

You can see configuration file example in redsocks.conf.example

- - -

iptables example

- -

You have to build iptables with connection tracking and REDIRECT target.

- -
-# Create new chain
-root# iptables -t nat -N REDSOCKS
-
-# Ignore LANs and some other reserved addresses.
-# See Wikipedia and RFC5735 for full list of reserved networks.
-root# iptables -t nat -A REDSOCKS -d 0.0.0.0/8 -j RETURN
-root# iptables -t nat -A REDSOCKS -d 10.0.0.0/8 -j RETURN
-root# iptables -t nat -A REDSOCKS -d 127.0.0.0/8 -j RETURN
-root# iptables -t nat -A REDSOCKS -d 169.254.0.0/16 -j RETURN
-root# iptables -t nat -A REDSOCKS -d 172.16.0.0/12 -j RETURN
-root# iptables -t nat -A REDSOCKS -d 192.168.0.0/16 -j RETURN
-root# iptables -t nat -A REDSOCKS -d 224.0.0.0/4 -j RETURN
-root# iptables -t nat -A REDSOCKS -d 240.0.0.0/4 -j RETURN
-
-# Anything else should be redirected to port 12345
-root# iptables -t nat -A REDSOCKS -p tcp -j REDIRECT --to-ports 12345
-
-# Any tcp connection made by `luser' should be redirected.
-root# iptables -t nat -A OUTPUT -p tcp -m owner --uid-owner luser -j REDSOCKS
-
-# You can also control that in more precise way using `gid-owner` from
-# iptables.
-root# groupadd socksified
-root# usermod --append --groups socksified luser
-root# iptables -t nat -A OUTPUT -p tcp -m owner --gid-owner socksified -j REDSOCKS
-
-# Now you can launch your specific application with GID `socksified` and it
-# will be... socksified. See following commands (numbers may vary).
-# Note: you may have to relogin to apply `usermod` changes.
-luser$ id
-uid=1000(luser) gid=1000(luser) groups=1000(luser),1001(socksified)
-luser$ sg socksified -c id
-uid=1000(luser) gid=1001(socksified) groups=1000(luser),1001(socksified)
-luser$ sg socksified -c "firefox"
-
-# If you want to configure socksifying router, you should look at
-# doc/iptables-packet-flow.png and doc/iptables-packet-flow-ng.png
-# Note, you should have proper `local_ip' value to get external packets with
-# redsocks, default 127.0.0.1 will not go. See iptables(8) manpage regarding
-# REDIRECT target for details.
-# Depending on your network configuration iptables conf. may be as easy as:
-root# iptables -t nat -A PREROUTING --in-interface eth_int -p tcp -j REDSOCKS
-
- -

Note about GID-based redirection

-

-Keep in mind, that changed GID affects filesystem permissions, so if your -application creates some files, the files will be created with luser:socksified -owner/group. So, if you're not the only user in the group `socksified` and your -umask allows to create group-readable files and your directory permissions, and -so on, blah-blah, etc. THEN you may expose your files to another user. -

-

-Ok, you have been warned. -

- -

Homepage

- -

Homepage: http://darkk.net.ru/redsocks/

- -

Mailing list: redsocks@librelist.com

- -

Mailing list also has archives.

- - -

TODO

- - - - -

Author

-This program was written by Leonid Evdokimov. - - - - -
- (~~~~~~~~~~\   __
- | jabber:  )  /  \
- | mailto: (  /|oo \
-  \_________\(_|  /_)
-              _ @/_ \
-             |     | \   \\
-      leon   | (*) |  \   ))  darkk.net.ru
-             |__U__| /  \//
-              _//|| _\   /
-             (_/(_|(____/
-                         Valid CSS!
-                         Valid XHTML 1.0 Transitional
-
- - - - diff --git a/README.md b/README.md new file mode 100644 index 00000000..5a8235c0 --- /dev/null +++ b/README.md @@ -0,0 +1,210 @@ +REDSOCKS2 +========= +[![Linux Build Status](https://github.com/semigodking/redsocks/workflows/linux_build/badge.svg)](https://github.com/semigodking/redsocks/actions) +[![FreeBSD Build Status](https://github.com/semigodking/redsocks/workflows/freebsd_build/badge.svg)](https://github.com/semigodking/redsocks/actions) + +This is a modified version of original redsocks. +The name is changed to REDSOCKS2 to distinguish with original redsocks. +REDSOCKS2 contains several new features besides many bug fixes to original +redsocks. + +1. Redirect TCP connections which are blocked via proxy automatically without +need of blacklist. +2. Redirect UDP based DNS requests via TCP connection. +3. Integrated [shadowsocks](http://shadowsocks.org/) proxy support. +4. Redirect TCP connections without proxy. +5. Redirect TCP connections via specified network interface. +6. UDP transparent proxy via shadowsocks proxy. +7. Support Ful-cone NAT Traversal when working with shadowsocks or socks5 proxy. +8. Integrated HTTPS proxy support(HTTP CONNECT over SSL). +9. Support TCP Fast Open on local server side and shadowsocks client side. +10. Support port reuse ([SO_REUSEPORT](https://lwn.net/Articles/542629/)). +11. Support IPv6. + +[Chinese Reference](https://github.com/semigodking/redsocks/wiki) + +HOW TO BUILD +------------ +### Prerequisites +The following libraries are required. + +* libevent2 +* OpenSSL >= 1.1.1 or mbedTLS >= 2.16 + +Note: PolarSSL is no longer supported. Use mbedTLS instead. + +### Steps +On general Linux, simply run command below to build with OpenSSL. + +``` +$ make +``` + +To compile with mbedTLS + +``` +$ make USE_CRYPTO_MBEDTLS=true +``` + +To compile static binaries (with Tomatoware) + +``` +$ make ENABLE_STATIC=true +``` + +By default, HTTPS proxy support is disabled. To enable this feature, you need to +compile like (Require libevent2 compiled with OpenSSL support): +``` +$ make ENABLE_HTTPS_PROXY=true +``` + +To compile without shadowsocks support: +``` +$ make DISABLE_SHADOWSOCKS=true +``` + +Since this variant of redsocks is customized for running with Openwrt, please +read documents here (http://wiki.openwrt.org/doc/devel/crosscompile) for how +to cross compile. + +### MacOS +To build on a MacOS system, you will have to install OpenSSL headers and libevent2 +For this, brew is your best friends +``` +$ brew install openssl libevent +``` +Makefile include the folder of openssl headers and lib installed by brew. + +To build with PF and run on MacOS, you will need some pf headers that are not included with a standard MacOS installation. +You can find them on this repository : https://github.com/apple/darwin-xnu +And the Makefile will going find this file for you + +Configurations +-------------- +Please see 'redsocks.conf.example' for whole picture of configuration file. +Below are additional sample configuration sections for different usage. +Operations required to iptables are not listed here. + +### Redirect Blocked Traffic via Proxy Automatically +To use the autoproxy feature, please change the redsocks section in +configuration file like this: + + redsocks { + bind = "192.168.1.1:1081"; + relay = "192.168.1.1:9050"; + type = socks5; // I use socks5 proxy for GFW'ed IP + autoproxy = 1; // I want autoproxy feature enabled on this section. + // timeout is meaningful when 'autoproxy' is non-zero. + // It specified timeout value when trying to connect to destination + // directly. Default is 10 seconds. When it is set to 0, default + // timeout value will be used. + // NOTE: decreasing the timeout value may lead increase of chance for + // normal IP to be misjudged. + timeout = 13; + //type = http-connect; + //login = username; + //password = passwd; + } + +### Redirect Blocked Traffic via VPN Automatically +Suppose you have VPN connection setup with interface tun0. You want all +all blocked traffic pass through via VPN connection while normal traffic +pass through via default internet connection. + + redsocks { + bind = "192.168.1.1:1081"; + interface = tun0; // Outgoing interface for blocked traffic + type = direct; + timeout = 13; + autoproxy = 1; + } + +### Redirect Blocked Traffic via shadowsocks proxy +Similar like other redsocks section. The encryption method is specified +by field 'login'. + + redsocks { + bind = "192.168.1.1:1080"; + type = shadowsocks; + relay = "192.168.1.1:8388"; + timeout = 13; + autoproxy = 1; + login = "aes-128-cfb"; // field 'login' is reused as encryption + // method of shadowsocks + password = "your password"; // Your shadowsocks password + } + + redudp { + bind = "127.0.0.1:1053"; + relay = "123.123.123.123:1082"; + type = shadowsocks; + login = rc4-md5; + password = "ss server password"; + dest = "8.8.8.8:53"; + udp_timeout = 3; + } + + +List of supported encryption methods(Compiled with OpenSSL >= 1.1.1): + + table + rc4 + rc4-md5 + aes-128-cfb + aes-192-cfb + aes-256-cfb + bf-cfb + camellia-128-cfb + camellia-192-cfb + camellia-256-cfb + cast5-cfb + des-cfb + idea-cfb + rc2-cfb + seed-cfb + aes-128-gcm + aes-192-gcm + aes-256-gcm + chacha20-ietf-poly1305 + +List of supported encryption methods(Compiled with mbedTLS >= 2.16): + + table + ARC4-128 + AES-128-CFB128 + AES-192-CFB128 + AES-256-CFB128 + BLOWFISH-CFB64 + CAMELLIA-128-CFB128 + CAMELLIA-192-CFB128 + CAMELLIA-256-CFB128 + AES-128-GCM + AES-192-GCM + AES-256-GCM + CHACHA20-POLY1305 + +> Note: +> chacha20-ietf-poly1305 requires OpenSSL >= 1.1.0 or mbedTLS >= 2.16 with ChachaPoly support. + +### Redirect UDP based DNS Request via TCP connection +Sending DNS request via TCP connection is one way to prevent from DNS +poisoning. You can redirect all UDP based DNS requests via TCP connection +with the following config section. + + tcpdns { + // Transform UDP DNS requests into TCP DNS requests. + // You can also redirect connections to external TCP DNS server to + // REDSOCKS transparent proxy via iptables. + bind = "192.168.1.1:1053"; // Local server to act as DNS server + tcpdns1 = "8.8.4.4:53"; // DNS server that supports TCP DNS requests + tcpdns2 = 8.8.8.8; // DNS server that supports TCP DNS requests + timeout = 4; // Timeout value for TCP DNS requests + } + +Then, you can either redirect all your DNS requests to the local IP:port +configured above by iptables, or just change your system default DNS upstream +server as the local IP:port configured above. + +AUTHOR +------ +[Zhuofei Wang](mailto:semigodking.com) semigodking@gmail.com diff --git a/autoproxy.c b/autoproxy.c new file mode 100644 index 00000000..dd0b7d09 --- /dev/null +++ b/autoproxy.c @@ -0,0 +1,752 @@ +/* redsocks2 - transparent TCP-to-proxy redirector + * Copyright (C) 2013-2017 Zhuofei Wang + * + * This code is based on redsocks project developed by Leonid Evdokimov. + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + + + +#include +#include +#include +#include +#include +#include +#include +#include +#include "utils.h" +#include "log.h" +#include "redsocks.h" +#include "parser.h" +#include "list.h" +#include "main.h" +#include "ipcache.h" + +typedef enum autoproxy_state_t { + /* Introduce subsystem */ + AUTOPROXY_NEW=10000, + AUTOPROXY_CONNECTED, + AUTOPROXY_CONFIRMED, + AUTOPROXY_SHUTDOWN, // Not used +} autoproxy_state; + +typedef struct autoproxy_client_t { + autoproxy_state state; + size_t data_recv; + size_t data_sent; + struct event * recv_timer_event; + short quick_check; // flag indicating quick check initiated. +} autoproxy_client; + + +void redsocks_event_error(struct bufferevent *buffev, short what, void *_arg); +static int auto_retry_or_drop(redsocks_client * client); +static void direct_relay_clientreadcb(struct bufferevent *from, void *_client); +static void auto_event_error(struct bufferevent *buffev, short what, void *_arg); + +#define QUICK_CONNECT_TIMEOUT_SECONDS 3 +#define NO_CHECK_SECONDS 60 + + +typedef struct autoproxy_config_t { + list_head list; // Make it a list to support multiple configurations + char * interface; // interface to be used for relay + uint16_t quick_connect_timeout; + uint16_t no_quick_check_seconds; +} autoproxy_config; + +static autoproxy_config default_config = { + .quick_connect_timeout = QUICK_CONNECT_TIMEOUT_SECONDS, + .no_quick_check_seconds = NO_CHECK_SECONDS, +}; + +static list_head configs = LIST_HEAD_INIT(configs); + +static parser_entry autoproxy_entries[] = +{ + { .key = "interface", .type = pt_pchar }, + { .key = "quick_connect_timeout", .type = pt_uint16 }, + { .key = "no_quick_check_seconds", .type = pt_uint16 }, + { } +}; + +static int autoproxy_onenter(parser_section *section) +{ + autoproxy_config * config = malloc(sizeof(*config)); + if (!config) { + parser_error(section->context, "Not enough memory"); + return -1; + } + + INIT_LIST_HEAD(&config->list); + /* initialize with default config */ + memcpy(config, &default_config, sizeof(*config)); + + for (parser_entry *entry = §ion->entries[0]; entry->key; entry++) + entry->addr = + (strcmp(entry->key, "interface") == 0) ? (void*)&config->interface : + (strcmp(entry->key, "quick_connect_timeout") == 0) ? (void*)&config->quick_connect_timeout: + (strcmp(entry->key, "no_quick_check_seconds") == 0) ? (void*)&config->no_quick_check_seconds: + NULL; + section->data = config; + return 0; +} + +static int autoproxy_onexit(parser_section *section) +{ + /* FIXME: Rewrite in bullet-proof style. There are memory leaks if config + * file is not correct, so correct on-the-fly config reloading is + * currently impossible. + */ + const char *err = NULL; + autoproxy_config * config = section->data; + + section->data = NULL; + for (parser_entry *entry = §ion->entries[0]; entry->key; entry++) + entry->addr = NULL; + + /* Check and update values here */ + if (!config->quick_connect_timeout) + err = "Timeout value for quick check can not be 0. Default: 3"; + + if (err) + parser_error(section->context, "%s", err); + else + // Add config to config list + list_add(&config->list, &configs); + + return err ? -1 : 0; +} + +static parser_section autoproxy_conf_section = +{ + .name = "autoproxy", + .entries = autoproxy_entries, + .onenter = autoproxy_onenter, + .onexit = autoproxy_onexit +}; + +app_subsys autoproxy_app_subsys = +{ +// .init = autoproxy_init, +// .fini = autoproxy_fini, + .conf_section = &autoproxy_conf_section, +}; + + +static autoproxy_config * get_config(redsocks_client * client) +{ + // TODO: By far, only the first configuration section takes effect. + // We need to find a proper way to let user specify which config section + // to associate with. + autoproxy_config * config = NULL; + if (!list_empty(&configs)) + { + list_for_each_entry(config, &configs, list) + break; + return config; + } + else + { + return &default_config; + } +} + +#define get_autoproxy_client(client) ((void*)(client + 1) + client->instance->relay_ss->payload_len) + +static void auto_release_recv_timer(autoproxy_client * aclient) +{ + // Cancel timer and release event object for timer + if (aclient->recv_timer_event) + { + event_del(aclient->recv_timer_event); + event_free(aclient->recv_timer_event); + aclient->recv_timer_event = NULL; + } +} + +static void auto_client_init(redsocks_client *client) +{ + autoproxy_client * aclient = get_autoproxy_client(client); + + memset((void *) aclient, 0, sizeof(autoproxy_client)); + aclient->state = AUTOPROXY_NEW; +} + +static void auto_client_fini(redsocks_client *client) +{ + autoproxy_client * aclient = get_autoproxy_client(client); + auto_release_recv_timer(aclient); +} + +static void on_connection_confirmed(redsocks_client *client) +{ + redsocks_log_error(client, LOG_DEBUG, "IP Confirmed"); + + if (client->destaddr.ss_family == AF_INET) + cache_del_addr((struct sockaddr_in *)&client->destaddr); +} + +static void on_connection_blocked(redsocks_client *client) +{ + redsocks_log_error(client, LOG_DEBUG, "IP Blocked"); +} + +static void auto_confirm_connection(redsocks_client * client) +{ + autoproxy_client * aclient = get_autoproxy_client(client); + + assert(aclient->state == AUTOPROXY_CONNECTED); + aclient->state = AUTOPROXY_CONFIRMED; + if (aclient->data_sent) + { + evbuffer_drain(bufferevent_get_input(client->client), aclient->data_sent); + aclient->data_sent = 0; + } + auto_release_recv_timer(aclient); + on_connection_confirmed(client); +} + +static void auto_recv_timeout_cb(evutil_socket_t fd, short events, void * arg) +{ + redsocks_client *client = arg; + autoproxy_client * aclient = get_autoproxy_client(client); + + redsocks_log_error(client, LOG_DEBUG, "RECV Timeout, state: %d, data_sent: %zu", aclient->state, aclient->data_sent); + assert(events & EV_TIMEOUT); + + redsocks_touch_client(client); + // Let's make connection confirmed + if (aclient->state == AUTOPROXY_CONNECTED) + auto_confirm_connection(client); + else + return; + + // No ERROR/EOF/data received before timeout, continue sending data + if (!(client->relay_evshut & EV_WRITE)) + { + if (bufferevent_write_buffer(client->relay, bufferevent_get_input(client->client)) == -1) + redsocks_log_errno(client, LOG_ERR, "bufferevent_write_buffer"); + if (!(client->client_evshut & EV_READ) && bufferevent_enable(client->client, EV_READ) == -1) + redsocks_log_errno(client, LOG_ERR, "bufferevent_enable"); + } +} + + +static void direct_relay_readcb_helper(redsocks_client *client, struct bufferevent *from, struct bufferevent *to) +{ + if (evbuffer_get_length(bufferevent_get_output(to)) < get_write_hwm(to)) + { + if (bufferevent_write_buffer(to, bufferevent_get_input(from)) == -1) + redsocks_log_errno(client, LOG_ERR, "bufferevent_write_buffer"); + if (bufferevent_enable(from, EV_READ) == -1) + redsocks_log_errno(client, LOG_ERR, "bufferevent_enable"); + } + else { + if (bufferevent_disable(from, EV_READ) == -1) + redsocks_log_errno(client, LOG_ERR, "bufferevent_disable"); + } +} + +// Caller should continue writing if this function returns 0. +// Otherwise, stop writing. +static int handle_write_to_relay(redsocks_client *client) +{ + autoproxy_client * aclient = get_autoproxy_client(client); + struct bufferevent * from = client->client; + struct bufferevent * to = client->relay; + size_t input_size = evbuffer_get_length(bufferevent_get_input(from)); + size_t output_size = evbuffer_get_length(bufferevent_get_output(to)); + + if (aclient->state == AUTOPROXY_CONNECTED ) + { + redsocks_log_error(client, LOG_DEBUG, "sent: %zu, recv: %zu, in:%zu, out:%zu", + aclient->data_sent, + aclient->data_recv, + input_size, + output_size + ); + /* Not send or receive data. */ + if (!aclient->data_sent && !aclient->data_recv) + { + /* Ensure we have data to send */ + if (input_size) + { + /* copy data from input to output of relay */ + aclient->data_sent = copy_evbuffer (to, from, 0); + return 1; + } + } + /* + * Relay reaceived data before writing to relay. + */ + else if (!aclient->data_sent && aclient->data_recv) + { + // TODO: + // In case we receive data before sending any data, + // should we confirm connection immediately or after + // sending data? + //aclient->data_sent = copy_evbuffer(to, from, 0); + auto_confirm_connection(client); + + } + /* aclient->state = AUTOPROXY_CONFIRMED; */ + /* We have writen data to relay, but got nothing until we are requested to + * write to it again. + */ + else if (aclient->data_sent && !aclient->data_recv) + { + /* No response from relay and no CONNECTION RESET, + Send more data. + */ + /* Write more data util input buffer of relay is full */ + if (input_size - aclient->data_sent > 0) /* we have more data waiting to be sent */ + { + aclient->data_sent += copy_evbuffer(to, from, aclient->data_sent); + } + + else if (output_size < aclient->data_sent /* data is sent out, more or less */ + && input_size == aclient->data_sent /* all data in read buffer is sent */ + && !aclient->recv_timer_event /* timer is not activated yet */ + ) + { + aclient->recv_timer_event = evtimer_new(bufferevent_get_base(to), auto_recv_timeout_cb, client); + if (aclient->recv_timer_event) + { + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 400000; + if (-1 == evtimer_add(aclient->recv_timer_event, &tv)) + { + redsocks_log_error(client, LOG_DEBUG, "Failed to add timer!"); + // In case we failed to add timer, it is abnormal. + // Let's confirm the connection directly so that normal service is not + // impacted. + auto_confirm_connection(client); + return 0; + } + } + } + return 1; + } + /* We sent data to and got data from relay. */ + else if (aclient->data_sent && aclient->data_recv) + { + /* No CONNECTION RESET error occur after sending data, good. */ + auto_confirm_connection(client); + } + } + return 0; +} + +// Caller should continue writing if this function returns 0. +// Otherwise, stop writing. +static int handle_write_to_client(redsocks_client *client) +{ + autoproxy_client * aclient = get_autoproxy_client(client); + struct bufferevent * from = client->relay; + size_t input_size = evbuffer_get_length(bufferevent_get_input(from)); + + if (aclient->state == AUTOPROXY_CONNECTED) + { + if (!aclient->data_recv) + { + aclient->data_recv = input_size; + if (input_size) + auto_confirm_connection(client); + } + } + return 0; +} + +static void direct_relay_clientreadcb(struct bufferevent *from, void *_client) +{ + redsocks_client *client = _client; + size_t input_size = evbuffer_get_length(bufferevent_get_input(from)); + + redsocks_log_error(client, LOG_DEBUG, "client in: %zu", input_size); + redsocks_touch_client(client); + if (handle_write_to_relay(client)) + return; + direct_relay_readcb_helper(client, client->client, client->relay); +} + + +static void direct_relay_relayreadcb(struct bufferevent *from, void *_client) +{ + redsocks_client *client = _client; + size_t input_size = evbuffer_get_length(bufferevent_get_input(from)); + + redsocks_log_error(client, LOG_DEBUG, "relay in: %zu", input_size); + redsocks_touch_client(client); + if (handle_write_to_client(client)) + return; + direct_relay_readcb_helper(client, client->relay, client->client); +} + +static void direct_relay_clientwritecb(struct bufferevent *to, void *_client) +{ + redsocks_client *client = _client; + struct bufferevent * from = client->relay; + size_t output_size = evbuffer_get_length(bufferevent_get_output(to)); + + redsocks_touch_client(client); + if (process_shutdown_on_write_(client, from, to)) + return; + if (handle_write_to_client(client)) + return; + if (output_size < get_write_hwm(to)) + { + if (bufferevent_write_buffer(to, bufferevent_get_input(from)) == -1) + redsocks_log_errno(client, LOG_ERR, "bufferevent_write_buffer"); + if (!(client->relay_evshut & EV_READ) && bufferevent_enable(from, EV_READ) == -1) + redsocks_log_errno(client, LOG_ERR, "bufferevent_enable"); + } +} + +static int process_shutdown_on_write_2(redsocks_client *client, struct bufferevent *from, struct bufferevent *to) +{ + autoproxy_client * aclient = get_autoproxy_client(client); + size_t input_size = evbuffer_get_length(bufferevent_get_input(from)); + unsigned short from_evshut = from == client->client ? client->client_evshut : client->relay_evshut; + unsigned short to_evshut = to == client->client ? client->client_evshut : client->relay_evshut; + + redsocks_log_error(client, LOG_DEBUG, "WCB %s, fs: %u, ts: %u, fin: %zu, fout: %zu, tin: %zu", + to == client->client?"client":"relay", + from_evshut, + to_evshut, + evbuffer_get_length(bufferevent_get_input(from)), + evbuffer_get_length(bufferevent_get_output(from)), + evbuffer_get_length(bufferevent_get_input(to))); + + if ((from_evshut & EV_READ) && !(to_evshut & EV_WRITE)) + { + if (input_size == 0 + || (input_size == aclient->data_sent && aclient->state == AUTOPROXY_CONNECTED)) + { + redsocks_shutdown(client, to, SHUT_WR, 0); + return 1; + } + } + return 0; +} + + +static void direct_relay_relaywritecb(struct bufferevent *to, void *_client) +{ + redsocks_client *client = _client; + autoproxy_client * aclient = get_autoproxy_client(client); + struct bufferevent * from = client->client; + size_t output_size = evbuffer_get_length(bufferevent_get_output(to)); + + redsocks_touch_client(client); + + if (process_shutdown_on_write_2(client, from, to)) + return; + if (handle_write_to_relay(client)) + return; + if (aclient->state == AUTOPROXY_CONFIRMED) + { + if (output_size < get_write_hwm(to)) + { + if (bufferevent_write_buffer(to, bufferevent_get_input(from)) == -1) + redsocks_log_errno(client, LOG_ERR, "bufferevent_write_buffer"); + if (!(client->client_evshut & EV_READ) && bufferevent_enable(from, EV_READ) == -1) + redsocks_log_errno(client, LOG_ERR, "bufferevent_enable"); + } + } +} + +static void auto_drop_relay(redsocks_client *client) +{ + int fd; + if (client->relay) + { + redsocks_log_error(client, LOG_DEBUG, "dropping relay only "); + fd = bufferevent_getfd(client->relay); + bufferevent_disable(client->relay, EV_READ|EV_WRITE); + bufferevent_free(client->relay); + redsocks_close(fd); + client->relay = NULL; + } + client->relay_evshut = 0; + client->relay_connected = 0; +} + +static int auto_retry(redsocks_client * client, int updcache) +{ + autoproxy_client * aclient = get_autoproxy_client(client); + int rc; + + if (aclient->state == AUTOPROXY_CONNECTED) + bufferevent_disable(client->client, EV_READ| EV_WRITE); + /* drop relay and update state, then retry with specified relay */ + if (updcache) + { + /* only add IP to cache when the IP is not in cache */ + if (client->destaddr.ss_family == AF_INET + && cache_get_addr_time((struct sockaddr_in *)&client->destaddr) == NULL) + { + char destaddr_str[RED_INET_ADDRSTRLEN]; + cache_add_addr((struct sockaddr_in *)&client->destaddr); + redsocks_log_error(client, LOG_DEBUG, "ADD IP to cache: %s", + red_inet_ntop(&client->destaddr, destaddr_str, sizeof(destaddr_str))); + } + } + + auto_release_recv_timer(aclient); + auto_drop_relay(client); + // restore callbacks for ordinary client. + bufferevent_setcb(client->client, NULL, NULL, redsocks_event_error, client); + // enable reading to handle EOF from client + if (!(client->client_evshut & EV_READ)) + bufferevent_enable(client->client, EV_READ); + + /* connect to relay */ + if (client->instance->relay_ss->connect_relay) + { + rc = client->instance->relay_ss->connect_relay(client); + // In case the underline relay system does not connect relay, + // it maybe is waiting for client read event. + // Take 'http-relay' for example. + if (!rc && !client->relay && evbuffer_get_length(bufferevent_get_input(client->client))) +#if LIBEVENT_VERSION_NUMBER >= 0x02010100 + bufferevent_trigger_event(client->client, EV_READ, 0); +#else + client->client->readcb(client->client, client); +#endif + } + else + rc = redsocks_connect_relay(client); + return rc; +} + +/* return 1 for drop, 0 for retry. */ +static int auto_retry_or_drop(redsocks_client * client) +{ + autoproxy_client * aclient = get_autoproxy_client(client); + + if (aclient->state == AUTOPROXY_NEW || aclient->state == AUTOPROXY_CONNECTED) + { + on_connection_blocked(client); + return auto_retry(client, 0); + } + /* drop */ + return 1; +} + +static void auto_relay_connected(struct bufferevent *buffev, void *_arg) +{ + redsocks_client *client = _arg; + autoproxy_client * aclient = get_autoproxy_client(client); + + assert(buffev == client->relay); + + redsocks_touch_client(client); + + if (!red_is_socket_connected_ok(buffev)) { + if (aclient->state == AUTOPROXY_NEW && !auto_retry_or_drop(client)) + return; + + redsocks_log_error(client, LOG_DEBUG, "failed to connect to destination"); + redsocks_drop_client(client); + return; + } + + /* update client state */ + aclient->state = AUTOPROXY_CONNECTED; + client->relay_connected = 1; + + /* We do not need to detect timeouts any more. + The two peers will handle it. */ + bufferevent_set_timeouts(client->relay, NULL, NULL); + + if (redsocks_start_relay(client)) + // redsocks_start_relay() drops client on failure + return; + /* overwrite theread callback to my function */ + bufferevent_setcb(client->client, direct_relay_clientreadcb, + direct_relay_clientwritecb, + auto_event_error, + client); + bufferevent_setcb(client->relay, direct_relay_relayreadcb, + direct_relay_relaywritecb, + auto_event_error, + client); + // Write any data received from client side to relay. + if (evbuffer_get_length(bufferevent_get_input(client->client))) + direct_relay_relaywritecb(client->relay, client); + return; +} + +// Note: before relay is connected, READING EOF/ERROR reported from client +// is handled by redsocks default ERROR handler. +static void auto_event_error(struct bufferevent *buffev, short what, void *_arg) +{ + redsocks_client *client = _arg; + autoproxy_client * aclient = get_autoproxy_client(client); + int saved_errno = errno; + + assert(buffev == client->relay || buffev == client->client); + redsocks_touch_client(client); + + if (!(what & BEV_EVENT_ERROR)) + errno = red_socket_geterrno(buffev); + redsocks_log_errno(client, LOG_DEBUG, "%s, errno(%d), State: %d, what: " event_fmt_str, + buffev == client->client?"client":"relay", + errno, aclient->state, event_fmt(what)); + if (buffev == client->relay) + { +/* + if (what & BEV_EVENT_CONNECTED) + { + auto_relay_connected(buffev, _arg); + return; + } +*/ + if ( aclient->state == AUTOPROXY_NEW + && what == (BEV_EVENT_WRITING | BEV_EVENT_TIMEOUT)) + { + // Update access time for IP fails again. + if (aclient->quick_check && client->destaddr.ss_family == AF_INET) + cache_touch_addr((struct sockaddr_in*)&client->destaddr); + + on_connection_blocked(client); + /* In case timeout occurs while connecting relay, we try to connect + to target via configured proxy. It is possible that the connection to + target can be set up a bit longer than the timeout value we set. + However, it is still better to make connection via proxy. */ + auto_retry(client, 1); + return; + } + + if (aclient->state == AUTOPROXY_NEW && saved_errno == ECONNRESET) + if (!auto_retry_or_drop(client)) + return; + + if (aclient->state == AUTOPROXY_CONNECTED + && what == (BEV_EVENT_READING|BEV_EVENT_ERROR) + /* No matter it is disconnected due to Connection Reset or any + other reason, we still have a chance to forward connection via + proxy. I prefer retry only if we got connection reset. + */ + && saved_errno == ECONNRESET) + { + if (!auto_retry_or_drop(client)) + return; + } + } + + if (what == (BEV_EVENT_READING|BEV_EVENT_EOF)) + { + // Timer cases: + // 1. READ EOF from relay (normal case, need to releae timer) + // 2. READ EOF from client (normal case, no need to release timer) + // 3. READ ERROR from client (abnormal, not recoverable) + // 4. READ ERROR from erlay (abnormal, not recoverable) + if (aclient->recv_timer_event && buffev == client->relay) + auto_confirm_connection(client); + + redsocks_shutdown(client, buffev, SHUT_RD, 1); + // Ensure the other party could send remaining data and SHUT_WR also + if (buffev == client->client) + { + if (!(client->relay_evshut & EV_WRITE)) + bufferevent_enable(client->relay, EV_WRITE); + } + else + { + if (!(client->client_evshut & EV_WRITE)) + bufferevent_enable(client->client, EV_WRITE); + } + } + else + { + redsocks_drop_client(client); + } +} + + +static int auto_connect_relay(redsocks_client *client) +{ + autoproxy_client * aclient = get_autoproxy_client(client); + autoproxy_config * config = get_config(client); + struct timeval tv = {client->instance->config.timeout, 0}; + time_t * acc_time = NULL; + time_t now = redsocks_time(NULL); + + if (aclient->state == AUTOPROXY_NEW) + { + if (client->destaddr.ss_family == AF_INET) + acc_time = cache_get_addr_time((struct sockaddr_in *)&client->destaddr); + if (acc_time) + { + redsocks_log_error(client, LOG_DEBUG, "Found dest IP in cache"); + // No quick check when the time passed since IP is added to cache is + // less than NO_CHECK_SECONDS. Just let it go via proxy. + if (config->no_quick_check_seconds == 0 + || now - *acc_time < config->no_quick_check_seconds) + return auto_retry(client, 0); + /* update timeout value for quick detection. + * Sometimes, good sites are added into cache due to occasionally + * connection timeout. It is annoying. So, decision is made to + * always try to connect to destination first when the destination + * is found in cache. + * For most destinations, the connection could be set up correctly + * in short time. And, for most blocked sites, we get connection + * reset almost immediately when connection is set up or when HTTP + * request is sent. + */ + tv.tv_sec = config->quick_connect_timeout; + aclient->quick_check = 1; + } + /* connect to target directly without going through proxy */ + client->relay = red_connect_relay( + config->interface, + &client->destaddr, + NULL, + auto_relay_connected, + auto_event_error, + client, + &tv); + if (!client->relay) { + // Failed to connect to destination directly, try again via proxy. + return auto_retry(client, 0); + } + } + else + { + redsocks_log_errno(client, LOG_ERR, "invalid state: %d", aclient->state); + redsocks_drop_client(client); + return -1; + } + return 0; +} + + +relay_subsys autoproxy_subsys = +{ + .name = "autoproxy", + .payload_len = sizeof(autoproxy_client), + .instance_payload_len = 0, + .readcb = direct_relay_relayreadcb, + .writecb = direct_relay_relaywritecb, + .init = auto_client_init, + .fini = auto_client_fini, + .connect_relay = auto_connect_relay, +}; + + +/* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ +/* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ diff --git a/base.c b/base.c index 7254ea01..ed6af4b5 100644 --- a/base.c +++ b/base.c @@ -14,6 +14,7 @@ * under the License. */ +#include #include #include #include @@ -31,21 +32,37 @@ # include # include #endif + #if defined USE_PF # include +#if defined _APPLE_ +#define PRIVATE +#endif # include +#if defined _APPLE_ +#define sport sxport.port +#define dport dxport.port +#define rdport rdxport.port +#undef PRIVATE +#endif # include -# include #endif +#ifdef __FreeBSD__ +# include +# include +#endif + +#include #include "log.h" #include "main.h" #include "parser.h" #include "redsocks.h" +#include "utils.h" typedef struct redirector_subsys_t { int (*init)(); void (*fini)(); - int (*getdestaddr)(int fd, const struct sockaddr_in *client, const struct sockaddr_in *bindaddr, struct sockaddr_in *destaddr); + int (*getdestaddr)(int fd, const struct sockaddr_storage *client, const struct sockaddr_storage *bindaddr, struct sockaddr_storage *destaddr); const char *name; // some subsystems may store data here: int private; @@ -62,6 +79,9 @@ typedef struct base_instance_t { bool log_debug; bool log_info; bool daemon; +#ifdef SO_REUSEPORT + bool reuseport; +#endif #if defined(TCP_KEEPIDLE) && defined(TCP_KEEPCNT) && defined(TCP_KEEPINTVL) uint16_t tcp_keepalive_time; uint16_t tcp_keepalive_probes; @@ -75,7 +95,7 @@ static base_instance instance = { .log_info = false, }; -#if defined __FreeBSD__ || defined USE_PF +#if defined __FreeBSD__ || defined USE_PF || defined __OpenBSD__ || defined __NetBSD__ static int redir_open_private(const char *fname, int flags) { int fd = open(fname, flags); @@ -105,10 +125,13 @@ static int redir_init_ipf() return redir_open_private(fname, O_RDONLY); } -static int getdestaddr_ipf(int fd, const struct sockaddr_in *client, const struct sockaddr_in *bindaddr, struct sockaddr_in *destaddr) +static int getdestaddr_ipf(int fd, + const struct sockaddr_storage *client, + const struct sockaddr_storage *bindaddr, + struct sockaddr_storage *destaddr) { int natfd = instance.redirector->private; - struct natlookup natLookup; + struct natlookup nl; int x; #if defined(IPFILTER_VERSION) && (IPFILTER_VERSION >= 4000027) struct ipfobj obj; @@ -118,17 +141,17 @@ static int getdestaddr_ipf(int fd, const struct sockaddr_in *client, const struc #if defined(IPFILTER_VERSION) && (IPFILTER_VERSION >= 4000027) obj.ipfo_rev = IPFILTER_VERSION; - obj.ipfo_size = sizeof(natLookup); - obj.ipfo_ptr = &natLookup; + obj.ipfo_size = sizeof(nl); + obj.ipfo_ptr = &nl; obj.ipfo_type = IPFOBJ_NATLOOKUP; obj.ipfo_offset = 0; #endif - natLookup.nl_inport = bindaddr->sin_port; - natLookup.nl_outport = client->sin_port; - natLookup.nl_inip = bindaddr->sin_addr; - natLookup.nl_outip = client->sin_addr; - natLookup.nl_flags = IPN_TCP; + nl.nl_inport = ((struct sockaddr_in *)bindaddr)->sin_port; + nl.nl_outport = ((struct sockaddr_in *)client)->sin_port; + nl.nl_inip = ((struct sockaddr_in *)bindaddr)->sin_addr; + nl.nl_outip = ((struct sockaddr_in *)client)->sin_addr; + nl.nl_flags = IPN_TCP; #if defined(IPFILTER_VERSION) && (IPFILTER_VERSION >= 4000027) x = ioctl(natfd, SIOCGNATL, &obj); #else @@ -140,10 +163,10 @@ static int getdestaddr_ipf(int fd, const struct sockaddr_in *client, const struc * this seems simpler. */ if (63 == siocgnatl_cmd) { - struct natlookup *nlp = &natLookup; + struct natlookup *nlp = &nl; x = ioctl(natfd, SIOCGNATL, &nlp); } else { - x = ioctl(natfd, SIOCGNATL, &natLookup); + x = ioctl(natfd, SIOCGNATL, &nl); } #endif if (x < 0) { @@ -151,9 +174,9 @@ static int getdestaddr_ipf(int fd, const struct sockaddr_in *client, const struc log_errno(LOG_WARNING, "ioctl(SIOCGNATL)\n"); return -1; } else { - destaddr->sin_family = AF_INET; - destaddr->sin_port = natLookup.nl_realport; - destaddr->sin_addr = natLookup.nl_realip; + destaddr->ss_family = AF_INET; + ((struct sockaddr_in *)destaddr)->sin_port = nl.nl_realport; + ((struct sockaddr_in *)destaddr)->sin_addr = nl.nl_realip; return 0; } } @@ -165,21 +188,42 @@ static int redir_init_pf() return redir_open_private("/dev/pf", O_RDWR); } +// FIXME: Support IPv6 static int getdestaddr_pf( - int fd, const struct sockaddr_in *client, const struct sockaddr_in *bindaddr, - struct sockaddr_in *destaddr) + int fd, + const struct sockaddr_storage *client, + const struct sockaddr_storage *bindaddr, + struct sockaddr_storage *destaddr) { int pffd = instance.redirector->private; struct pfioc_natlook nl; int saved_errno; - char clientaddr_str[INET6_ADDRSTRLEN], bindaddr_str[INET6_ADDRSTRLEN]; + char clientaddr_str[RED_INET_ADDRSTRLEN], bindaddr_str[RED_INET_ADDRSTRLEN]; memset(&nl, 0, sizeof(struct pfioc_natlook)); - nl.saddr.v4.s_addr = client->sin_addr.s_addr; - nl.sport = client->sin_port; - nl.daddr.v4.s_addr = bindaddr->sin_addr.s_addr; - nl.dport = bindaddr->sin_port; - nl.af = AF_INET; + if (client->ss_family == AF_INET) { + nl.saddr.v4 = ((const struct sockaddr_in *)client)->sin_addr; + nl.sport = ((struct sockaddr_in *)client)->sin_port; + } + else if (client->ss_family == AF_INET6) { + memcpy(&nl.saddr.v6, &((const struct sockaddr_in6 *)client)->sin6_addr, sizeof(struct in6_addr)); + nl.sport = ((struct sockaddr_in6 *)client)->sin6_port; + } + else { + goto fail; + } + if (bindaddr->ss_family == AF_INET) { + nl.daddr.v4 = ((const struct sockaddr_in *)bindaddr)->sin_addr; + nl.dport = ((struct sockaddr_in *)bindaddr)->sin_port; + } + else if (bindaddr->ss_family == AF_INET6) { + memcpy(&nl.daddr.v6, &((const struct sockaddr_in6 *)bindaddr)->sin6_addr, sizeof(struct in6_addr)); + nl.dport = ((struct sockaddr_in6 *)bindaddr)->sin6_port; + } + else { + goto fail; + } + nl.af = client->ss_family; // Use same address family ass client nl.proto = IPPROTO_TCP; nl.direction = PF_OUT; @@ -194,32 +238,50 @@ static int getdestaddr_pf( goto fail; } } - destaddr->sin_family = AF_INET; - destaddr->sin_port = nl.rdport; - destaddr->sin_addr = nl.rdaddr.v4; + destaddr->ss_family = nl.af; + if (nl.af == AF_INET) { + ((struct sockaddr_in *)destaddr)->sin_port = nl.rdport; + ((struct sockaddr_in *)destaddr)->sin_addr = nl.rdaddr.v4; + } + else { + ((struct sockaddr_in6 *)destaddr)->sin6_port = nl.rdport; + memcpy(&(((struct sockaddr_in6 *)destaddr)->sin6_addr), &nl.rdaddr.v6, sizeof(struct in6_addr)); + } return 0; fail: saved_errno = errno; - if (!inet_ntop(client->sin_family, &client->sin_addr, clientaddr_str, sizeof(clientaddr_str))) - strncpy(clientaddr_str, "???", sizeof(clientaddr_str)); - if (!inet_ntop(bindaddr->sin_family, &bindaddr->sin_addr, bindaddr_str, sizeof(bindaddr_str))) - strncpy(bindaddr_str, "???", sizeof(bindaddr_str)); - + red_inet_ntop(client, clientaddr_str, sizeof(clientaddr_str)); + red_inet_ntop(bindaddr, bindaddr_str, sizeof(bindaddr_str)); errno = saved_errno; - log_errno(LOG_WARNING, "ioctl(DIOCNATLOOK {src=%s:%d, dst=%s:%d})", - clientaddr_str, ntohs(nl.sport), bindaddr_str, ntohs(nl.dport)); + log_errno(LOG_WARNING, "ioctl(DIOCNATLOOK {src=%s, dst=%s})", + clientaddr_str, bindaddr_str); return -1; } #endif #ifdef USE_IPTABLES -static int getdestaddr_iptables(int fd, const struct sockaddr_in *client, const struct sockaddr_in *bindaddr, struct sockaddr_in *destaddr) +static int getdestaddr_iptables( + int fd, + const struct sockaddr_storage *client, + const struct sockaddr_storage *bindaddr, + struct sockaddr_storage *destaddr) { socklen_t socklen = sizeof(*destaddr); int error; - error = getsockopt(fd, SOL_IP, SO_ORIGINAL_DST, destaddr, &socklen); +#ifdef SOL_IPV6 + int sol = (client->ss_family == AF_INET6) ? SOL_IPV6 : SOL_IP; +#else + int sol = SOL_IP; +#endif + error = getsockopt(fd, sol, SO_ORIGINAL_DST, destaddr, &socklen); +#ifdef SOL_IPV6 + if (error && sol == SOL_IPV6) { + sol = SOL_IP; + error = getsockopt(fd, sol, SO_ORIGINAL_DST, destaddr, &socklen); + } +#endif if (error) { log_errno(LOG_WARNING, "getsockopt"); return -1; @@ -228,7 +290,11 @@ static int getdestaddr_iptables(int fd, const struct sockaddr_in *client, const } #endif -static int getdestaddr_generic(int fd, const struct sockaddr_in *client, const struct sockaddr_in *bindaddr, struct sockaddr_in *destaddr) +static int getdestaddr_generic( + int fd, + const struct sockaddr_storage *client, + const struct sockaddr_storage *bindaddr, + struct sockaddr_storage *destaddr) { socklen_t socklen = sizeof(*destaddr); int error; @@ -241,7 +307,11 @@ static int getdestaddr_generic(int fd, const struct sockaddr_in *client, const s return 0; } -int getdestaddr(int fd, const struct sockaddr_in *client, const struct sockaddr_in *bindaddr, struct sockaddr_in *destaddr) +int getdestaddr( + int fd, + const struct sockaddr_storage *client, + const struct sockaddr_storage *bindaddr, + struct sockaddr_storage *destaddr) { return instance.redirector->getdestaddr(fd, client, bindaddr, destaddr); } @@ -250,9 +320,11 @@ int apply_tcp_keepalive(int fd) { struct { int level, option, value; } opt[] = { { SOL_SOCKET, SO_KEEPALIVE, 1 }, +#if defined(TCP_KEEPIDLE) && defined(TCP_KEEPCNT) && defined(TCP_KEEPINTVL) { IPPROTO_TCP, TCP_KEEPIDLE, instance.tcp_keepalive_time }, { IPPROTO_TCP, TCP_KEEPCNT, instance.tcp_keepalive_probes }, { IPPROTO_TCP, TCP_KEEPINTVL, instance.tcp_keepalive_intvl }, +#endif }; for (int i = 0; i < SIZEOF_ARRAY(opt); ++i) { if (opt[i].value) { @@ -266,6 +338,22 @@ int apply_tcp_keepalive(int fd) return 0; } +int apply_reuseport(int fd) +{ +#ifdef SO_REUSEPORT + if (!instance.reuseport) + return 0; + + int opt = 1; + int rc = setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt)); + if (rc == -1) + log_errno(LOG_ERR, "setsockopt"); + return rc; +#else + return -1; +#endif +} + static redirector_subsys redirector_subsystems[] = { #ifdef __FreeBSD__ @@ -297,6 +385,9 @@ static parser_entry base_entries[] = { .key = "tcp_keepalive_time", .type = pt_uint16, .addr = &instance.tcp_keepalive_time }, { .key = "tcp_keepalive_probes", .type = pt_uint16, .addr = &instance.tcp_keepalive_probes }, { .key = "tcp_keepalive_intvl", .type = pt_uint16, .addr = &instance.tcp_keepalive_intvl }, +#endif +#ifdef SO_REUSEPORT + { .key = "reuseport", .type = pt_bool, .addr = &instance.reuseport}, #endif { } }; diff --git a/base.h b/base.h index 2f56f2d5..5519ee2f 100644 --- a/base.h +++ b/base.h @@ -1,8 +1,9 @@ #ifndef BASE_H_SUN_JUN__3_20_15_57_2007 #define BASE_H_SUN_JUN__3_20_15_57_2007 -int getdestaddr(int fd, const struct sockaddr_in *client, const struct sockaddr_in *bindaddr, struct sockaddr_in *destaddr); +int getdestaddr(int fd, const struct sockaddr_storage *client, const struct sockaddr_storage *bindaddr, struct sockaddr_storage *destaddr); int apply_tcp_keepalive(int fd); +int apply_reuseport(int fd); /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ diff --git a/debian/redsocks.conf b/debian/redsocks.conf index 011aecad..8c074466 100644 --- a/debian/redsocks.conf +++ b/debian/redsocks.conf @@ -33,7 +33,6 @@ base { redirector = iptables; } -redsocks { /* `local_ip' defaults to 127.0.0.1 for security reasons, * use 0.0.0.0 if you want to listen on every interface. * `local_*' are used as port to redirect to. @@ -41,17 +40,48 @@ redsocks { local_ip = 127.0.0.1; local_port = 12345; + // listen() queue length. Default value is SOMAXCONN and it should be + // good enough for most of us. + // listenq = 128; // SOMAXCONN equals 128 on my Linux box. + + // `max_accept_backoff` is a delay to retry `accept()` after accept + // failure (e.g. due to lack of file descriptors). It's measured in + // milliseconds and maximal value is 65535. `min_accept_backoff` is + // used as initial backoff value and as a damper for `accept() after + // close()` logic. + // min_accept_backoff = 100; + // max_accept_backoff = 60000; + // `ip' and `port' are IP and tcp-port of proxy-server // You can also use hostname instead of IP, only one (random) // address of multihomed host will be used. - ip = 127.0.0.1; + // The two fields are meaningless when proxy type is 'direct'. + ip = example.org; port = 1080; - // known types: socks4, socks5, http-connect, http-relay + // New types: direct, shadowsocks type = socks5; - // login = "foobar"; + // Specify interface for outgoing connections when 'direct' type + // is used. This is useful when you have multiple connections to + // internet or you have VPN connections. + // interface = tun0; + + // Change this parameter to 1 if you want auto proxy feature. + // When autoproxy is set to non-zero, the connection to target + // will be made directly first. If direct connection to target + // fails for timeout/connection refuse, redsocks will try to + // connect to target via the proxy. + autoproxy = 0; + // timeout is meaningful when 'autoproxy' is non-zero. + // It specified timeout value when trying to connect to destination + // directly. Default is 10 seconds. When it is set to 0, default + // timeout value will be used. + timeout = 10; + + // login = "foobar";// field 'login' is reused as encryption + // method of shadowsocks // password = "baz"; } @@ -65,9 +95,13 @@ redudp { // `ip' and `port' of socks5 proxy server. ip = 10.0.0.1; port = 1080; - login = username; + login = username;// field 'login' is reused as encryption + // method of shadowsocks password = pazzw0rd; + // know types: socks5, shadowsocks + type = socks5; + // kernel does not give us this information, so we have to duplicate it // in both iptables rules and configuration file. By the way, you can // set `local_ip' to 127.45.67.89 if you need more than 65535 ports to @@ -76,16 +110,51 @@ redudp { dest_ip = 8.8.8.8; dest_port = 53; + // Do not set it large if this section is for DNS requests. Otherwise, + // you may encounter out of file descriptor problem. For DNS requests, + // 10s is adequate. udp_timeout = 30; - udp_timeout_stream = 180; + // udp_timeout_stream = 180; } -dnstc { - // fake and really dumb DNS server that returns "truncated answer" to - // every query via UDP, RFC-compliant resolver should repeat same query - // via TCP in this case. - local_ip = 127.0.0.1; - local_port = 5300; +tcpdns { + // Transform UDP DNS requests into TCP DNS requests. + // You can also redirect connections to external TCP DNS server to + // REDSOCKS transparent proxy via iptables. + local_ip = 192.168.1.1; // Local server to act as DNS server + local_port = 1053; // UDP port to receive UDP DNS requests + tcpdns1 = 8.8.4.4; // DNS server that supports TCP DNS requests + tcpdns2 = 8.8.8.8; // DNS server that supports TCP DNS requests + timeout = 4; // Timeout value for TCP DNS requests } +autoproxy { + no_quick_check_seconds = 60; // Directly relay traffic to proxy if an IP + // is found blocked in cache and it has been + // added into cache no earlier than this + // specified number of seconds. + // Set it to 0 if you do not want to perform + // quick check when an IP is found in blocked + // IP cache, thus the connection will be + // redirected to proxy immediately. + quick_connect_timeout = 3; // Timeout value when performing quick + // connection check if an IP is found blocked + // in cache. +} + +ipcache { + // Configure IP cache + cache_size = 4; // Maximum number of IP's in 1K. + stale_time = 900; // Seconds to stale an IP in cache since it is added + // into cahce. + // Set it to 0 to disable cache stale. + port_check = 1; // Whether to distinguish port number in address + cache_file = "/tmp/ipcache.txt"; // File used to store blocked IP's in cache. + autosave_interval = 3600; // Interval for saving ip cache into file. + // Set it to 0 to disable autosave. + // When autosave_interval and stale_time are both 0, IP cache behaves like + // a static blacklist. +} + + // you can add more `redsocks' and `redudp' sections if you need. diff --git a/direct.c b/direct.c new file mode 100644 index 00000000..d7179efc --- /dev/null +++ b/direct.c @@ -0,0 +1,103 @@ +/* redsocks2 - transparent TCP-to-proxy redirector + * Copyright (C) 2013-2017 Zhuofei Wang + * + * This code is based on redsocks project developed by Leonid Evdokimov. + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + + +#include +#include +#include +#include +#include "parser.h" +#include "log.h" +#include "main.h" +#include "redsocks.h" +#include "utils.h" + + +int redsocks_start_relay(redsocks_client *client); +void redsocks_touch_client(redsocks_client *client); +void redsocks_event_error(struct bufferevent *buffev, short what, void *_arg); +void redsocks_relay_connected(struct bufferevent *buffev, void *_arg); + +static void direct_relay_init(redsocks_client *client) +{ + client->state = 0; +} + +static void direct_relay_fini(redsocks_client *client) +{ +} + +static void direct_write_cb(struct bufferevent *buffev, void *_arg) +{ + redsocks_client *client = _arg; + redsocks_touch_client(client); + if (client->state == 0) + { + client->state = 1; + if (redsocks_start_relay(client)) + // Failed to start relay. Connection is dropped. + return; + // Write any data received from client to relay + struct evbuffer * input = bufferevent_get_input(client->client); + if (evbuffer_get_length(input)) + if (bufferevent_write_buffer(client->relay, input) == -1) + redsocks_log_errno(client, LOG_ERR, "bufferevent_write_buffer"); + } +} + +static int direct_connect_relay(redsocks_client *client) +{ + redsocks_instance * instance = client->instance; + char * interface = instance->config.interface; + struct timeval tv = {instance->config.timeout, 0}; + struct sockaddr_storage * destaddr = &client->destaddr; + + if (instance->config.relay) { + destaddr = &instance->config.relayaddr; + } + // Allowing binding relay socket to specified IP for outgoing connections + client->relay = red_connect_relay( + interface, + destaddr, + NULL, + redsocks_relay_connected, + redsocks_event_error, + client, + &tv); + if (!client->relay) + { + redsocks_log_errno(client, LOG_ERR, "red_connect_relay"); + redsocks_drop_client(client); + return -1; + } + return 0; +} + +relay_subsys direct_connect_subsys = +{ + .name = "direct", + .payload_len = 0, + .instance_payload_len = 0, + .writecb = direct_write_cb, + .init = direct_relay_init, + .fini = direct_relay_fini, + .connect_relay = direct_connect_relay, +}; + + +/* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ +/* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ diff --git a/dnstc.c b/dnstc.c deleted file mode 100644 index 5f9feddf..00000000 --- a/dnstc.c +++ /dev/null @@ -1,259 +0,0 @@ -/* redsocks - transparent TCP-to-proxy redirector - * Copyright (C) 2007-2011 Leonid Evdokimov - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy - * of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "list.h" -#include "log.h" -#include "parser.h" -#include "main.h" -#include "redsocks.h" -#include "dnstc.h" -#include "utils.h" - -#define dnstc_log_error(prio, msg...) \ - redsocks_log_write_plain(__FILE__, __LINE__, __func__, 0, &clientaddr, &self->config.bindaddr, prio, ## msg) -#define dnstc_log_errno(prio, msg...) \ - redsocks_log_write_plain(__FILE__, __LINE__, __func__, 1, &clientaddr, &self->config.bindaddr, prio, ## msg) - -static void dnstc_fini_instance(dnstc_instance *instance); -static int dnstc_fini(); - -typedef struct dns_header_t { - uint16_t id; - uint8_t qr_opcode_aa_tc_rd; - uint8_t ra_z_rcode; - uint16_t qdcount; - uint16_t ancount; - uint16_t nscount; - uint16_t arcount; -} PACKED dns_header; - -#define DNS_QR 0x80 -#define DNS_TC 0x02 -#define DNS_Z 0x70 - -/*********************************************************************** - * Logic - */ -static void dnstc_pkt_from_client(int fd, short what, void *_arg) -{ - dnstc_instance *self = _arg; - struct sockaddr_in clientaddr; - union { - char raw[0xFFFF]; // UDP packet can't be larger then that - dns_header h; - } buf; - ssize_t pktlen, outgoing; - - assert(fd == EVENT_FD(&self->listener)); - pktlen = red_recv_udp_pkt(fd, buf.raw, sizeof(buf), &clientaddr, NULL); - if (pktlen == -1) - return; - - if (pktlen <= sizeof(dns_header)) { - dnstc_log_error(LOG_INFO, "incomplete DNS request"); - return; - } - - if (1 - && (buf.h.qr_opcode_aa_tc_rd & DNS_QR) == 0 /* query */ - && (buf.h.ra_z_rcode & DNS_Z) == 0 /* Z is Zero */ - && buf.h.qdcount /* some questions */ - && !buf.h.ancount && !buf.h.nscount && !buf.h.arcount /* no answers */ - ) { - buf.h.qr_opcode_aa_tc_rd |= DNS_QR; - buf.h.qr_opcode_aa_tc_rd |= DNS_TC; - outgoing = sendto(fd, buf.raw, pktlen, 0, - (struct sockaddr*)&clientaddr, sizeof(clientaddr)); - if (outgoing != pktlen) - dnstc_log_errno(LOG_WARNING, "sendto: I was sending %zd bytes, but only %zd were sent.", - pktlen, outgoing); - else - dnstc_log_error(LOG_INFO, "sent truncated DNS reply"); - } - else { - dnstc_log_error(LOG_INFO, "malformed DNS request"); - } -} - -/*********************************************************************** - * Init / shutdown - */ -static parser_entry dnstc_entries[] = -{ - { .key = "local_ip", .type = pt_in_addr }, - { .key = "local_port", .type = pt_uint16 }, - { } -}; - -static list_head instances = LIST_HEAD_INIT(instances); - -static int dnstc_onenter(parser_section *section) -{ - dnstc_instance *instance = calloc(1, sizeof(*instance)); - if (!instance) { - parser_error(section->context, "Not enough memory"); - return -1; - } - - INIT_LIST_HEAD(&instance->list); - instance->config.bindaddr.sin_family = AF_INET; - instance->config.bindaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - - for (parser_entry *entry = §ion->entries[0]; entry->key; entry++) - entry->addr = - (strcmp(entry->key, "local_ip") == 0) ? (void*)&instance->config.bindaddr.sin_addr : - (strcmp(entry->key, "local_port") == 0) ? (void*)&instance->config.bindaddr.sin_port : - NULL; - section->data = instance; - return 0; -} - -static int dnstc_onexit(parser_section *section) -{ - dnstc_instance *instance = section->data; - - section->data = NULL; - for (parser_entry *entry = §ion->entries[0]; entry->key; entry++) - entry->addr = NULL; - - instance->config.bindaddr.sin_port = htons(instance->config.bindaddr.sin_port); - - list_add(&instance->list, &instances); - - return 0; -} - -static int dnstc_init_instance(dnstc_instance *instance) -{ - /* FIXME: dnstc_fini_instance is called in case of failure, this - * function will remove instance from instances list - result - * looks ugly. - */ - int error; - int fd = -1; - - fd = socket(AF_INET, SOCK_DGRAM, 0); - if (fd == -1) { - log_errno(LOG_ERR, "socket"); - goto fail; - } - - error = bind(fd, (struct sockaddr*)&instance->config.bindaddr, sizeof(instance->config.bindaddr)); - if (error) { - log_errno(LOG_ERR, "bind"); - goto fail; - } - - error = fcntl_nonblock(fd); - if (error) { - log_errno(LOG_ERR, "fcntl"); - goto fail; - } - - event_set(&instance->listener, fd, EV_READ | EV_PERSIST, dnstc_pkt_from_client, instance); - error = event_add(&instance->listener, NULL); - if (error) { - log_errno(LOG_ERR, "event_add"); - goto fail; - } - - return 0; - -fail: - dnstc_fini_instance(instance); - - if (fd != -1) { - if (close(fd) != 0) - log_errno(LOG_WARNING, "close"); - } - - return -1; -} - -/* Drops instance completely, freeing its memory and removing from - * instances list. - */ -static void dnstc_fini_instance(dnstc_instance *instance) -{ - if (event_initialized(&instance->listener)) { - if (event_del(&instance->listener) != 0) - log_errno(LOG_WARNING, "event_del"); - if (close(EVENT_FD(&instance->listener)) != 0) - log_errno(LOG_WARNING, "close"); - memset(&instance->listener, 0, sizeof(instance->listener)); - } - - list_del(&instance->list); - - memset(instance, 0, sizeof(*instance)); - free(instance); -} - -static int dnstc_init() -{ - dnstc_instance *tmp, *instance = NULL; - - // TODO: init debug_dumper - - list_for_each_entry_safe(instance, tmp, &instances, list) { - if (dnstc_init_instance(instance) != 0) - goto fail; - } - - return 0; - -fail: - dnstc_fini(); - return -1; -} - -static int dnstc_fini() -{ - dnstc_instance *tmp, *instance = NULL; - - list_for_each_entry_safe(instance, tmp, &instances, list) - dnstc_fini_instance(instance); - - return 0; -} - -static parser_section dnstc_conf_section = -{ - .name = "dnstc", - .entries = dnstc_entries, - .onenter = dnstc_onenter, - .onexit = dnstc_onexit -}; - -app_subsys dnstc_subsys = -{ - .init = dnstc_init, - .fini = dnstc_fini, - .conf_section = &dnstc_conf_section, -}; - -/* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ -/* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ diff --git a/dnstc.h b/dnstc.h deleted file mode 100644 index 8aaf28be..00000000 --- a/dnstc.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef DNSTC_H -#define DNSTC_H - -typedef struct dnstc_config_t { - struct sockaddr_in bindaddr; -} dnstc_config; - -typedef struct dnstc_instance_t { - list_head list; - dnstc_config config; - struct event listener; -} dnstc_instance; - -/* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ -/* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ -#endif /* REDUDP_H */ diff --git a/encrypt.c b/encrypt.c new file mode 100644 index 00000000..86ff1875 --- /dev/null +++ b/encrypt.c @@ -0,0 +1,1146 @@ +/* + * encrypt.c - Manage the global encryptor + * + * Copyright (C) 2013 - 2015, Max Lv + * + * This file is part of the shadowsocks-libev. + * + * shadowsocks-libev is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * shadowsocks-libev is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with shadowsocks-libev; see the file COPYING. If not, see + * . + */ + +#include + +#if defined(USE_CRYPTO_OPENSSL) + +/* OpenSSL 3.0+ compatibility */ +#if OPENSSL_VERSION_NUMBER >= 0x30000000L +#include +#include +#define EVP_CIPHER_CTX_new_compat() EVP_CIPHER_CTX_new() +#define EVP_CIPHER_CTX_free_compat(ctx) EVP_CIPHER_CTX_free(ctx) +#define EVP_CIPHER_CTX_init_compat(ctx) +#define EVP_CIPHER_CTX_cleanup_compat(ctx) +#else +/* OpenSSL 1.1.x and earlier */ +#define EVP_CIPHER_CTX_new_compat() EVP_CIPHER_CTX_new() +#define EVP_CIPHER_CTX_free_compat(ctx) EVP_CIPHER_CTX_free(ctx) +#define EVP_CIPHER_CTX_init_compat(ctx) EVP_CIPHER_CTX_init(ctx) +#define EVP_CIPHER_CTX_cleanup_compat(ctx) EVP_CIPHER_CTX_cleanup(ctx) +#endif + +#elif defined(USE_CRYPTO_MBEDTLS) + +#include +#include +#include +#include +#define CIPHER_UNSUPPORTED "unsupported" + +#include +#ifdef _WIN32 +#include +#include +#else +#include +#endif + +#endif + +#include "encrypt.h" + +#define AEAD_TAG_LEN 16 +#define AEAD_NONCE_LEN 12 +#define AEAD_CHUNK_MAX 0x3FFF /* SIP004: payload length capped at 16383 */ + +/* HKDF-SHA1: RFC 5869. info = "ss-subkey" per SIP004 */ +static int hkdf_sha1(const uint8_t *key, int key_len, + const uint8_t *salt, int salt_len, + uint8_t *out, int out_len) +{ + const char *info = "ss-subkey"; + int info_len = 9; +#if defined(USE_CRYPTO_OPENSSL) + uint8_t prk[20]; + unsigned int prk_len = sizeof(prk); + if (!HMAC(EVP_sha1(), salt, salt_len, key, key_len, prk, &prk_len)) + return -1; + /* HKDF-Expand: T(i) = HMAC-SHA1(PRK, T(i-1) || info || i) */ + uint8_t t[20], buf[20 + 9 + 1]; + unsigned int t_len = 0; + int done = 0; + for (uint8_t i = 1; done < out_len; i++) { + int blen = t_len + info_len + 1; + memcpy(buf, t, t_len); + memcpy(buf + t_len, info, info_len); + buf[t_len + info_len] = i; + t_len = sizeof(t); + if (!HMAC(EVP_sha1(), prk, prk_len, buf, blen, t, &t_len)) + return -1; + int copy = min((int)t_len, out_len - done); + memcpy(out + done, t, copy); + done += copy; + } + return 0; +#elif defined(USE_CRYPTO_MBEDTLS) + uint8_t prk[20]; + const mbedtls_md_info_t *sha1 = mbedtls_md_info_from_type(MBEDTLS_MD_SHA1); + if (mbedtls_md_hmac(sha1, salt, salt_len, key, key_len, prk) != 0) + return -1; + uint8_t t[20], buf[20 + 9 + 1]; + size_t t_len = 0; + int done = 0; + for (uint8_t i = 1; done < out_len; i++) { + int blen = (int)t_len + info_len + 1; + memcpy(buf, t, t_len); + memcpy(buf + t_len, info, info_len); + buf[t_len + info_len] = i; + if (mbedtls_md_hmac(sha1, prk, sizeof(prk), buf, blen, t) != 0) + return -1; + t_len = 20; + int copy = min((int)t_len, out_len - done); + memcpy(out + done, t, copy); + done += copy; + } + return 0; +#endif +} + +/* Build 12-byte little-endian nonce from counter */ +static void make_nonce(uint8_t nonce[AEAD_NONCE_LEN], uint64_t counter) +{ + memset(nonce, 0, AEAD_NONCE_LEN); + /* little-endian */ + for (int i = 0; i < 8; i++) + nonce[i] = (counter >> (8 * i)) & 0xff; +} + +/* Single AEAD encrypt: plaintext -> ciphertext + tag. Returns 1 on success. */ +static int aead_encrypt(const uint8_t *subkey, int key_len, int is_chacha, + const uint8_t nonce[AEAD_NONCE_LEN], + const uint8_t *plain, int plen, + uint8_t *out) /* out must hold plen + AEAD_TAG_LEN */ +{ +#if defined(USE_CRYPTO_OPENSSL) + const EVP_CIPHER *cipher = is_chacha ? EVP_chacha20_poly1305() : + (key_len == 16) ? EVP_aes_128_gcm() : + (key_len == 24) ? EVP_aes_192_gcm() : + EVP_aes_256_gcm(); + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + int ok = 0, clen = 0, flen = 0; + if (!ctx) return 0; + if (!EVP_EncryptInit_ex(ctx, cipher, NULL, subkey, nonce)) goto done; + if (!EVP_EncryptUpdate(ctx, out, &clen, plain, plen)) goto done; + if (!EVP_EncryptFinal_ex(ctx, out + clen, &flen)) goto done; + int ctrl = is_chacha ? EVP_CTRL_AEAD_GET_TAG : EVP_CTRL_GCM_GET_TAG; + if (!EVP_CIPHER_CTX_ctrl(ctx, ctrl, AEAD_TAG_LEN, out + clen + flen)) goto done; + ok = 1; +done: + EVP_CIPHER_CTX_free(ctx); + return ok; +#elif defined(USE_CRYPTO_MBEDTLS) + if (is_chacha) { + mbedtls_chachapoly_context ctx; + mbedtls_chachapoly_init(&ctx); + int ok = 0; + if (mbedtls_chachapoly_setkey(&ctx, subkey) != 0) goto done_cp_enc; + if (mbedtls_chachapoly_encrypt_and_tag(&ctx, plen, nonce, + NULL, 0, plain, out, out + plen) != 0) goto done_cp_enc; + ok = 1; +done_cp_enc: + mbedtls_chachapoly_free(&ctx); + return ok; + } + mbedtls_gcm_context gcm; + mbedtls_gcm_init(&gcm); + int ok = 0; + if (mbedtls_gcm_setkey(&gcm, MBEDTLS_CIPHER_ID_AES, subkey, key_len * 8) != 0) goto done_enc; + if (mbedtls_gcm_crypt_and_tag(&gcm, MBEDTLS_GCM_ENCRYPT, plen, + nonce, AEAD_NONCE_LEN, NULL, 0, + plain, out, AEAD_TAG_LEN, out + plen) != 0) goto done_enc; + ok = 1; +done_enc: + mbedtls_gcm_free(&gcm); + return ok; +#endif +} + +/* Single AEAD decrypt: ciphertext + tag -> plaintext. Returns 1 on success. */ +static int aead_decrypt(const uint8_t *subkey, int key_len, int is_chacha, + const uint8_t nonce[AEAD_NONCE_LEN], + const uint8_t *in, int clen, /* clen excludes tag */ + const uint8_t *tag, + uint8_t *out) +{ +#if defined(USE_CRYPTO_OPENSSL) + const EVP_CIPHER *cipher = is_chacha ? EVP_chacha20_poly1305() : + (key_len == 16) ? EVP_aes_128_gcm() : + (key_len == 24) ? EVP_aes_192_gcm() : + EVP_aes_256_gcm(); + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + int ok = 0, plen = 0, flen = 0; + if (!ctx) return 0; + if (!EVP_DecryptInit_ex(ctx, cipher, NULL, subkey, nonce)) goto done; + int ctrl = is_chacha ? EVP_CTRL_AEAD_SET_TAG : EVP_CTRL_GCM_SET_TAG; + if (!EVP_CIPHER_CTX_ctrl(ctx, ctrl, AEAD_TAG_LEN, (void *)tag)) goto done; + if (!EVP_DecryptUpdate(ctx, out, &plen, in, clen)) goto done; + if (!EVP_DecryptFinal_ex(ctx, out + plen, &flen)) goto done; + ok = 1; +done: + EVP_CIPHER_CTX_free(ctx); + return ok; +#elif defined(USE_CRYPTO_MBEDTLS) + if (is_chacha) { + mbedtls_chachapoly_context ctx; + mbedtls_chachapoly_init(&ctx); + int ok = 0; + if (mbedtls_chachapoly_setkey(&ctx, subkey) != 0) goto done_cp_dec; + if (mbedtls_chachapoly_auth_decrypt(&ctx, clen, nonce, + NULL, 0, tag, in, out) != 0) goto done_cp_dec; + ok = 1; +done_cp_dec: + mbedtls_chachapoly_free(&ctx); + return ok; + } + mbedtls_gcm_context gcm; + mbedtls_gcm_init(&gcm); + int ok = 0; + if (mbedtls_gcm_setkey(&gcm, MBEDTLS_CIPHER_ID_AES, subkey, key_len * 8) != 0) goto done_dec; + if (mbedtls_gcm_auth_decrypt(&gcm, clen, nonce, AEAD_NONCE_LEN, + NULL, 0, tag, AEAD_TAG_LEN, in, out) != 0) goto done_dec; + ok = 1; +done_dec: + mbedtls_gcm_free(&gcm); + return ok; +#endif +} + +#define OFFSET_ROL(p, o) ((uint64_t)(*(p + o)) << (8 * o)) + +#ifdef DEBUG +static void dump(char *tag, char *text, int len) +{ + int i; + printf("%s: ", tag); + for (i = 0; i < len; i++) { + printf("0x%02x ", (uint8_t)text[i]); + } + printf("\n"); +} +#endif + +static const char * supported_ciphers[] = +{ + "table", + "rc4", + "rc4-md5", + "aes-128-cfb", + "aes-192-cfb", + "aes-256-cfb", + "bf-cfb", + "camellia-128-cfb", + "camellia-192-cfb", + "camellia-256-cfb", + "cast5-cfb", + "des-cfb", + "idea-cfb", + "rc2-cfb", + "seed-cfb", + "aes-128-gcm", + "aes-192-gcm", + "aes-256-gcm", + "chacha20-ietf-poly1305", +}; + +#ifdef USE_CRYPTO_MBEDTLS +static const char * supported_ciphers_mbedtls[] = +{ + "table", + "ARC4-128", + "ARC4-128", + "AES-128-CFB128", + "AES-192-CFB128", + "AES-256-CFB128", + "BLOWFISH-CFB64", + "CAMELLIA-128-CFB128", + "CAMELLIA-192-CFB128", + "CAMELLIA-256-CFB128", + CIPHER_UNSUPPORTED, + CIPHER_UNSUPPORTED, + CIPHER_UNSUPPORTED, + CIPHER_UNSUPPORTED, + CIPHER_UNSUPPORTED, + "AES-128-GCM", + "AES-192-GCM", + "AES-256-GCM", + "CHACHA20-POLY1305", +}; +#endif + +#define CIPHER_NUM (sizeof(supported_ciphers)/sizeof(supported_ciphers[0])) + +/* Check if the method uses GCM mode (AEAD cipher) */ +static int is_aead_mode(int method) +{ + return (method == AES_128_GCM || method == AES_192_GCM || method == AES_256_GCM + || method == CHACHA20_IETF_POLY1305); +} + +static int random_compare(const void *_x, const void *_y, uint32_t i, + uint64_t a) +{ + uint8_t x = *((uint8_t *)_x); + uint8_t y = *((uint8_t *)_y); + return a % (x + i) - a % (y + i); +} + +static void merge(uint8_t *left, int llength, uint8_t *right, + int rlength, uint32_t salt, uint64_t key) +{ + uint8_t *ltmp = (uint8_t *)malloc(llength * sizeof(uint8_t)); + uint8_t *rtmp = (uint8_t *)malloc(rlength * sizeof(uint8_t)); + + if (!ltmp || !rtmp) { + free(ltmp); + free(rtmp); + return; + } + + uint8_t *ll = ltmp; + uint8_t *rr = rtmp; + + uint8_t *result = left; + + memcpy(ltmp, left, llength * sizeof(uint8_t)); + memcpy(rtmp, right, rlength * sizeof(uint8_t)); + + while (llength > 0 && rlength > 0) { + if (random_compare(ll, rr, salt, key) <= 0) { + *result = *ll; + ++ll; + --llength; + } else { + *result = *rr; + ++rr; + --rlength; + } + ++result; + } + + if (llength > 0) { + while (llength > 0) { + *result = *ll; + ++result; + ++ll; + --llength; + } + } else { + while (rlength > 0) { + *result = *rr; + ++result; + ++rr; + --rlength; + } + } + + free(ltmp); + free(rtmp); +} + +static void merge_sort(uint8_t array[], int length, + uint32_t salt, uint64_t key) +{ + uint8_t middle; + uint8_t *left, *right; + int llength; + + if (length <= 1) { + return; + } + + middle = length / 2; + + llength = length - middle; + + left = array; + right = array + llength; + + merge_sort(left, llength, salt, key); + merge_sort(right, middle, salt, key); + merge(left, llength, right, middle, salt, key); +} + +static unsigned char *enc_md5(const unsigned char *d, size_t n, unsigned char *md) +{ +#if defined(USE_CRYPTO_OPENSSL) + static unsigned char m[16]; + if (md == NULL) { + md = m; + } + MD5(d, n, md); + return md; +#elif defined(USE_CRYPTO_MBEDTLS) + static unsigned char m[16]; + if (md == NULL) { + md = m; + } + mbedtls_md5(d, n, md); + return md; +#endif +} + +static void enc_table_init(enc_info * info, const char *pass) +{ + uint32_t i; + uint64_t key = 0; + uint8_t *digest; + + info->enc_table = malloc(256); + info->dec_table = malloc(256); + + digest = enc_md5((const uint8_t *)pass, strlen(pass), NULL); + + for (i = 0; i < 8; i++) { + key += OFFSET_ROL(digest, i); + } + + for (i = 0; i < 256; ++i) { + info->enc_table[i] = i; + } + for (i = 1; i < 1024; ++i) { + merge_sort(info->enc_table, 256, i, key); + } + for (i = 0; i < 256; ++i) { + // gen decrypt table from encrypt table + info->dec_table[info->enc_table[i]] = i; + } +} + +int cipher_iv_size(const cipher_kt_t *cipher) +{ +#if defined(USE_CRYPTO_OPENSSL) + return EVP_CIPHER_iv_length(cipher); +#elif defined(USE_CRYPTO_MBEDTLS) + if (cipher == NULL) + return 0; + mbedtls_cipher_context_t ctx; + mbedtls_cipher_init(&ctx); + if (mbedtls_cipher_setup(&ctx, cipher) != 0) { + mbedtls_cipher_free(&ctx); + return 0; + } + int iv_size = mbedtls_cipher_get_iv_size(&ctx); + mbedtls_cipher_free(&ctx); + return iv_size; +#endif +} + +int cipher_key_size(const cipher_kt_t *cipher) +{ +#if defined(USE_CRYPTO_OPENSSL) + return EVP_CIPHER_key_length(cipher); +#elif defined(USE_CRYPTO_MBEDTLS) + if (cipher == NULL) + return 0; + mbedtls_cipher_context_t ctx; + mbedtls_cipher_init(&ctx); + if (mbedtls_cipher_setup(&ctx, cipher) != 0) { + mbedtls_cipher_free(&ctx); + return 0; + } + int key_bits = mbedtls_cipher_get_key_bitlen(&ctx); + mbedtls_cipher_free(&ctx); + return key_bits / 8; +#endif +} + +int bytes_to_key(const cipher_kt_t *cipher, const digest_type_t *md, + const uint8_t *pass, uint8_t *key, uint8_t *iv) +{ + size_t datal; + datal = strlen((const char *)pass); +#if defined(USE_CRYPTO_OPENSSL) + return EVP_BytesToKey(cipher, md, NULL, pass, datal, 1, key, iv); +#elif defined(USE_CRYPTO_MBEDTLS) + mbedtls_md_context_t c; + unsigned char md_buf[MAX_MD_SIZE]; + int niv; + int nkey; + int addmd; + unsigned int mds; + unsigned int i; + int rv; + + nkey = cipher_key_size(cipher); + niv = cipher_iv_size(cipher); + rv = nkey; + if (pass == NULL) { + return nkey; + } + + mbedtls_md_init(&c); + if (mbedtls_md_setup(&c, md, 0) != 0) { + mbedtls_md_free(&c); + return 0; + } + addmd = 0; + mds = mbedtls_md_get_size(md); + for (;; ) { + int error; + do { + error = 1; + if (mbedtls_md_starts(&c) != 0) { + break; + } + if (addmd) { + if (mbedtls_md_update(&c, &(md_buf[0]), mds) != 0) { + break; + } + } else { + addmd = 1; + } + if (mbedtls_md_update(&c, pass, datal) != 0) { + break; + } + if (mbedtls_md_finish(&c, &(md_buf[0])) != 0) { + break; + } + error = 0; + } while (0); + if (error) { + mbedtls_md_free(&c); + memset(md_buf, 0, MAX_MD_SIZE); + return 0; + } + + i = 0; + if (nkey) { + for (;; ) { + if (nkey == 0) { + break; + } + if (i == mds) { + break; + } + if (key != NULL) { + *(key++) = md_buf[i]; + } + nkey--; + i++; + } + } + if (niv && (i != mds)) { + for (;; ) { + if (niv == 0) { + break; + } + if (i == mds) { + break; + } + if (iv != NULL) { + *(iv++) = md_buf[i]; + } + niv--; + i++; + } + } + if ((nkey == 0) && (niv == 0)) { + break; + } + } + mbedtls_md_free(&c); + memset(md_buf, 0, MAX_MD_SIZE); + return rv; +#endif +} + +int rand_bytes(uint8_t *output, int len) +{ +#if defined(USE_CRYPTO_OPENSSL) + return RAND_bytes(output, len); +#elif defined(USE_CRYPTO_MBEDTLS) + static mbedtls_entropy_context ec = {}; + static mbedtls_ctr_drbg_context cd_ctx = {}; + static unsigned char rand_initialised = 0; + const size_t blen = min(len, MBEDTLS_CTR_DRBG_MAX_REQUEST); + + if (!rand_initialised) { +#ifdef _WIN32 + HCRYPTPROV hProvider; + union { + unsigned __int64 seed; + BYTE buffer[8]; + } rand_buffer; + + hProvider = 0; + if (CryptAcquireContext(&hProvider, 0, 0, PROV_RSA_FULL, \ + CRYPT_VERIFYCONTEXT | CRYPT_SILENT)) { + CryptGenRandom(hProvider, 8, rand_buffer.buffer); + CryptReleaseContext(hProvider, 0); + } else { + rand_buffer.seed = (unsigned __int64)clock(); + } +#else + FILE *urand; + union { + uint64_t seed; + uint8_t buffer[8]; + } rand_buffer; + + urand = fopen("/dev/urandom", "r"); + if (urand) { + int read = fread(&rand_buffer.seed, sizeof(rand_buffer.seed), 1, + urand); + fclose(urand); + if (read <= 0) { + rand_buffer.seed = (uint64_t)clock(); + } + } else { + rand_buffer.seed = (uint64_t)clock(); + } +#endif + mbedtls_entropy_init(&ec); + if (mbedtls_ctr_drbg_seed(&cd_ctx, mbedtls_entropy_func, &ec, + (const unsigned char *)rand_buffer.buffer, 8) != 0) { + mbedtls_ctr_drbg_free(&cd_ctx); + mbedtls_entropy_free(&ec); + return 0; + } + rand_initialised = 1; + } + while (len > 0) { + if (mbedtls_ctr_drbg_random(&cd_ctx, output, blen) != 0) { + return 0; + } + output += blen; + len -= blen; + } + return 1; +#endif +} + +const cipher_kt_t *get_cipher_type(int method) +{ + if (method <= TABLE || method >= CIPHER_NUM) { + //LOGE("get_cipher_type(): Illegal method"); + return NULL; + } + + if (method == RC4_MD5) { + method = RC4; + } +#if defined(USE_CRYPTO_OPENSSL) + if (method == CHACHA20_IETF_POLY1305) + return EVP_get_cipherbyname("chacha20-poly1305"); + const char *ciphername = supported_ciphers[method]; + return EVP_get_cipherbyname(ciphername); +#elif defined(USE_CRYPTO_MBEDTLS) + const char *mbedtls_name = supported_ciphers_mbedtls[method]; + if (strcmp(mbedtls_name, CIPHER_UNSUPPORTED) == 0) { + //LOGE("Cipher %s currently is not supported by mbedTLS library", + // ciphername); + return NULL; + } + return mbedtls_cipher_info_from_string(mbedtls_name); +#endif +} + +const digest_type_t *get_digest_type(const char *digest) +{ + if (digest == NULL) { + //LOGE("get_digest_type(): Digest name is null"); + return NULL; + } + +#if defined(USE_CRYPTO_OPENSSL) + return EVP_get_digestbyname(digest); +#elif defined(USE_CRYPTO_MBEDTLS) + return mbedtls_md_info_from_string(digest); +#endif +} + +static int cipher_context_init(const enc_info * info, cipher_ctx_t *ctx, int enc) +{ + int method = info->method; + if (method <= TABLE || method >= CIPHER_NUM) { + // Illegal method + return -1; + } +#if defined(USE_CRYPTO_OPENSSL) + cipher_evp_t evp = EVP_CIPHER_CTX_new_compat(); + if (evp == NULL) { + // Cannot allocate cipher context + return -1; + } + ctx->evp = evp; + + const cipher_kt_t *cipher = get_cipher_type(method); + if (cipher == NULL) { + // Cipher is not found in OpenSSL library + EVP_CIPHER_CTX_free_compat(evp); + ctx->evp = NULL; + return -1; + } + EVP_CIPHER_CTX_init_compat(evp); + if (!EVP_CipherInit_ex(evp, cipher, NULL, NULL, NULL, enc)) { + // Cannot initialize cipher + EVP_CIPHER_CTX_free_compat(evp); + ctx->evp = NULL; + return -1; + } + if (!EVP_CIPHER_CTX_set_key_length(evp, info->key_len)) { + EVP_CIPHER_CTX_cleanup_compat(evp); + EVP_CIPHER_CTX_free_compat(evp); + ctx->evp = NULL; + // Invalid key length + return -1; + } + if (is_aead_mode(method)) { + /* GCM mode - disable padding and set tag length */ + EVP_CIPHER_CTX_set_padding(evp, 0); + /* Tag length will be set after initialization */ + } else if (method > RC4_MD5) { + EVP_CIPHER_CTX_set_padding(evp, 1); + } +#elif defined(USE_CRYPTO_MBEDTLS) + mbedtls_cipher_context_t *evp = &ctx->evp; + const cipher_kt_t *cipher = get_cipher_type(method); + if (cipher == NULL) { + // Cipher is not found in mbedTLS library + return -1; + } + mbedtls_cipher_init(evp); + if (mbedtls_cipher_setup(evp, cipher) != 0) { + mbedtls_cipher_free(evp); + return -1; + } +#endif + return 0; +} + +static void cipher_context_set_iv(const enc_info * info, cipher_ctx_t *ctx, uint8_t *iv, size_t iv_len, + int enc) +{ + const unsigned char *true_key; + + if (iv == NULL) { + //LOGE("cipher_context_set_iv(): IV is null"); + return; + } + + if (enc) { + rand_bytes(iv, iv_len); + } + if (info->method == RC4_MD5) { + unsigned char key_iv[32]; + memcpy(key_iv, info->key, 16); + memcpy(key_iv + 16, iv, 16); + true_key = enc_md5(key_iv, 32, NULL); + iv_len = 0; + } else { + true_key = info->key; + } + +#if defined(USE_CRYPTO_OPENSSL) + cipher_evp_t evp = ctx->evp; + if (evp == NULL) { + //LOGE("cipher_context_set_iv(): Cipher context is null"); + return; + } + if (!EVP_CipherInit_ex(evp, NULL, NULL, true_key, iv, enc)) { + EVP_CIPHER_CTX_cleanup_compat(evp); + //FATAL("Cannot set key and IV"); + } +#elif defined(USE_CRYPTO_MBEDTLS) + mbedtls_cipher_context_t *evp = &ctx->evp; + if (mbedtls_cipher_setkey(evp, true_key, info->key_len * 8, + enc ? MBEDTLS_ENCRYPT : MBEDTLS_DECRYPT) != 0) { + mbedtls_cipher_free(evp); + //FATAL("Cannot set mbedTLS cipher key"); + } + if (mbedtls_cipher_set_iv(evp, iv, iv_len) != 0) { + mbedtls_cipher_free(evp); + //FATAL("Cannot set mbedTLS cipher IV"); + } + if (mbedtls_cipher_reset(evp) != 0) { + mbedtls_cipher_free(evp); + //FATAL("Cannot reset mbedTLS cipher context"); + } +#endif + +#ifdef DEBUG + dump("IV", (char *)iv, iv_len); +#endif +} + +static void cipher_context_release(enc_info * info, cipher_ctx_t *ctx) +{ +#if defined(USE_CRYPTO_OPENSSL) + cipher_evp_t evp = ctx->evp; + if (evp != NULL) { + EVP_CIPHER_CTX_cleanup_compat(evp); + EVP_CIPHER_CTX_free_compat(evp); + ctx->evp = NULL; + } +#elif defined(USE_CRYPTO_MBEDTLS) + mbedtls_cipher_context_t *evp = &ctx->evp; + mbedtls_cipher_free(evp); +#endif +} + +static int cipher_context_update(cipher_ctx_t *ctx, uint8_t *output, int *olen, + const uint8_t *input, int ilen) +{ +#if defined(USE_CRYPTO_OPENSSL) + EVP_CIPHER_CTX *evp = ctx->evp; + return EVP_CipherUpdate(evp, (uint8_t *)output, olen, + (const uint8_t *)input, (size_t)ilen); +#elif defined(USE_CRYPTO_MBEDTLS) + size_t outlen = *olen; + int ret = mbedtls_cipher_update(&ctx->evp, (const uint8_t *)input, (size_t)ilen, + (uint8_t *)output, &outlen); + *olen = (int)outlen; + return (ret == 0) ? 1 : 0; +#endif +} + +/* Calculate buffer size required for encrypt/decrypt data */ +size_t ss_calc_buffer_size(struct enc_ctx * ctx, size_t ilen) +{ + int method = ctx->info->method; + if (is_aead_mode(method)) { + /* + * SIP004 TCP format: + * first call: [salt] + N * ([2+TAG] + [payload+TAG]) + * subsequent: N * ([2+TAG] + [payload+TAG]) + * Worst case: each byte could be its own chunk. + * In practice ilen <= AEAD_CHUNK_MAX, so one chunk: + * (2 + AEAD_TAG_LEN) + (ilen + AEAD_TAG_LEN) + */ + size_t salt_len = ctx->init ? 0 : ctx->info->key_len; + size_t chunks = (ilen + AEAD_CHUNK_MAX - 1) / AEAD_CHUNK_MAX; + if (chunks == 0) chunks = 1; + return salt_len + chunks * (2 + AEAD_TAG_LEN + AEAD_CHUNK_MAX + AEAD_TAG_LEN); + } +#if defined(USE_CRYPTO_OPENSSL) + const cipher_kt_t *cipher = get_cipher_type(method); + if (cipher == NULL) + return ilen; + if (ctx->init) + return ilen + EVP_CIPHER_block_size(cipher); + else + return EVP_CIPHER_iv_length(cipher) + ilen + EVP_CIPHER_block_size(cipher); +#elif defined(USE_CRYPTO_MBEDTLS) + const cipher_kt_t *cipher = get_cipher_type(method); + if (cipher == NULL) + return ilen; + if (ctx->init) + return ilen + mbedtls_cipher_get_block_size(&ctx->evp.evp); + else + return mbedtls_cipher_get_iv_size(&ctx->evp.evp) + ilen + mbedtls_cipher_get_block_size(&ctx->evp.evp); +#endif +} + +int ss_encrypt(struct enc_ctx *ctx, char *plaintext, size_t plen, + char * ciphertext, size_t * clen) +{ + if (ctx != NULL && ctx->info->method != TABLE) { + if (is_aead_mode(ctx->info->method)) { + /* + * SIP004 TCP AEAD encrypt: + * First call: prepend salt, derive subkey via HKDF-SHA1 + * Each chunk: [enc(2-byte-len) + TAG] [enc(payload) + TAG] + * Nonce: 12-byte little-endian counter, incremented per AE op + */ + uint8_t *out = (uint8_t *)ciphertext; + size_t out_len = 0; + int key_len = ctx->info->key_len; + int is_chacha = (ctx->info->method == CHACHA20_IETF_POLY1305); + + if (!ctx->init) { + /* Generate salt and derive subkey */ + rand_bytes(out, key_len); + if (hkdf_sha1(ctx->info->key, key_len, out, key_len, + ctx->subkey, key_len) != 0) + return 0; + out += key_len; + out_len += key_len; + ctx->counter = 0; + ctx->init = 1; + } + + const uint8_t *src = (const uint8_t *)plaintext; + size_t remaining = plen; + uint8_t nonce[AEAD_NONCE_LEN]; + + while (remaining > 0) { + uint16_t chunk = (uint16_t)min(remaining, (size_t)AEAD_CHUNK_MAX); + + /* Encrypt 2-byte length */ + uint8_t len_buf[2] = { (chunk >> 8) & 0xff, chunk & 0xff }; + make_nonce(nonce, ctx->counter++); + if (!aead_encrypt(ctx->subkey, key_len, is_chacha, nonce, + len_buf, 2, out)) + return 0; + out += 2 + AEAD_TAG_LEN; + out_len += 2 + AEAD_TAG_LEN; + + /* Encrypt payload */ + make_nonce(nonce, ctx->counter++); + if (!aead_encrypt(ctx->subkey, key_len, is_chacha, nonce, + src, chunk, out)) + return 0; + out += chunk + AEAD_TAG_LEN; + out_len += chunk + AEAD_TAG_LEN; + + src += chunk; + remaining -= chunk; + } + *clen = out_len; + return 1; + } + + int err = 1; + int iv_len = 0; + int p_len = plen, c_len = plen; + if (!ctx->init) { + iv_len = ctx->info->iv_len; + } + + if (!ctx->init) { + uint8_t iv[MAX_IV_LENGTH]; + cipher_context_set_iv(ctx->info, &ctx->evp, iv, iv_len, 1); + memcpy(ciphertext, iv, iv_len); + ctx->counter = 0; + ctx->init = 1; + } + + err = cipher_context_update(&ctx->evp, + (uint8_t *)(ciphertext + iv_len), + &c_len, (const uint8_t *)plaintext, + p_len); + if (!err) + return 0; + +#ifdef DEBUG + dump("PLAIN", plaintext, p_len); + dump("CIPHER", ciphertext + iv_len, c_len); +#endif + *clen = iv_len + c_len; + return 1; + } else { + char *begin = plaintext; + while (plaintext < begin + plen) { + *ciphertext = (char)ctx->info->enc_table[(uint8_t)*plaintext]; + plaintext++; + ciphertext++; + } + *clen = plen; + return 1; + } +} + +/* You need to ensure you have enough output buffer allocated */ +int ss_decrypt(struct enc_ctx *ctx, char *ciphertext, size_t clen, + char *plaintext, size_t *olen) +{ + if (ctx != NULL && ctx->info->method != TABLE) { + if (is_aead_mode(ctx->info->method)) { + /* + * SIP004 TCP AEAD decrypt: + * First call: read salt, derive subkey via HKDF-SHA1 + * Each chunk: decrypt [enc(2-byte-len)+TAG] then [enc(payload)+TAG] + */ + const uint8_t *in = (const uint8_t *)ciphertext; + size_t in_remaining = clen; + uint8_t *out = (uint8_t *)plaintext; + size_t out_len = 0; + int key_len = ctx->info->key_len; + int is_chacha = (ctx->info->method == CHACHA20_IETF_POLY1305); + uint8_t nonce[AEAD_NONCE_LEN]; + + if (!ctx->init) { + if (in_remaining < (size_t)key_len) + return 0; + if (hkdf_sha1(ctx->info->key, key_len, in, key_len, + ctx->subkey, key_len) != 0) + return 0; + in += key_len; + in_remaining -= key_len; + ctx->counter = 0; + ctx->init = 1; + } + + while (in_remaining > 0) { + /* Need at least encrypted length field */ + if (in_remaining < 2 + AEAD_TAG_LEN) + break; + + /* Decrypt length */ + uint8_t len_plain[2]; + make_nonce(nonce, ctx->counter); + if (!aead_decrypt(ctx->subkey, key_len, is_chacha, nonce, + in, 2, in + 2, len_plain)) + return 0; + ctx->counter++; + + uint16_t chunk = ((uint16_t)len_plain[0] << 8) | len_plain[1]; + if (chunk > AEAD_CHUNK_MAX) + return 0; + + in += 2 + AEAD_TAG_LEN; + in_remaining -= 2 + AEAD_TAG_LEN; + + /* Need full encrypted payload */ + if (in_remaining < (size_t)(chunk + AEAD_TAG_LEN)) + break; + + /* Decrypt payload */ + make_nonce(nonce, ctx->counter); + if (!aead_decrypt(ctx->subkey, key_len, is_chacha, nonce, + in, chunk, in + chunk, out)) + return 0; + ctx->counter++; + + out += chunk; + out_len += chunk; + in += chunk + AEAD_TAG_LEN; + in_remaining -= chunk + AEAD_TAG_LEN; + } + *olen = out_len; + return 1; + } + + int p_len = clen; + int iv_len = 0; + int err = 1; + + if (!ctx->init) { + iv_len = ctx->info->iv_len; + p_len -= iv_len; + cipher_context_set_iv(ctx->info, &ctx->evp, (uint8_t *)ciphertext, iv_len, 0); + ctx->counter = 0; + ctx->init = 1; + } + + err = cipher_context_update(&ctx->evp, (uint8_t *)plaintext, &p_len, + (const uint8_t *)(ciphertext + iv_len), + clen - iv_len); + if (!err) + return 0; + + *olen = p_len; + return 1; + } else { + char *begin = ciphertext; + while (ciphertext < begin + clen) { + *plaintext = (char)ctx->info->dec_table[(uint8_t)*ciphertext]; + ciphertext++; + plaintext++; + } + *olen = clen; + return 1; + } +} + +int enc_ctx_init(enc_info * info, struct enc_ctx *ctx, int enc) +{ + memset(ctx, 0, sizeof(struct enc_ctx)); + ctx->info = info; + if (is_aead_mode(info->method)) + return 0; /* GCM uses per-operation contexts, no persistent ctx needed */ + return cipher_context_init(info, &ctx->evp, enc); +} + +void enc_ctx_free(struct enc_ctx *ctx) +{ + if (!is_aead_mode(ctx->info->method)) + cipher_context_release(ctx->info, &ctx->evp); +} + +static int enc_key_init(enc_info * info, int method, const char *pass) +{ + if (method <= TABLE || method >= CIPHER_NUM) + return -1; + +#if defined(USE_CRYPTO_OPENSSL) + /* OpenSSL 3.0+ - algorithms are loaded automatically */ +#if OPENSSL_VERSION_NUMBER < 0x30000000L + OpenSSL_add_all_algorithms(); +#endif +#endif + + uint8_t iv[MAX_IV_LENGTH]; + + const cipher_kt_t *cipher = NULL; + + cipher = (cipher_kt_t *)get_cipher_type(method); + + if (cipher == NULL) + return -1; + + const digest_type_t *md = get_digest_type("MD5"); + if (md == NULL) + return -1; + + info->key_len = bytes_to_key(cipher, md, (const uint8_t *)pass, info->key, iv); + if (info->key_len == 0) { + //FATAL("Cannot generate key and IV"); + return -1; + } + if (method == RC4_MD5) { + info->iv_len = 16; + } else { + info->iv_len = cipher_iv_size(cipher); + } + info->method = method; + return method; +} + +int enc_init(enc_info * info, const char *pass, const char *method) +{ + memset((void *)info, 0, sizeof(enc_info)); + int m = TABLE; + if (method != NULL) { + for (m = TABLE; m < CIPHER_NUM; m++) { + if (strcmp(method, supported_ciphers[m]) == 0) { + break; + } + } + if (m >= CIPHER_NUM) + // Invalid encryption method + return -1; + } + if (m == TABLE) { + enc_table_init(info, pass); + } else { + m = enc_key_init(info, m, pass); + } + return m; +} + +void enc_free(enc_info * info) +{ + if (info->enc_table) + { + free(info->enc_table); + info->enc_table = NULL; + } + if (info->dec_table) + { + free(info->dec_table); + info->dec_table = NULL; + } + +} diff --git a/encrypt.h b/encrypt.h new file mode 100644 index 00000000..4b618dd4 --- /dev/null +++ b/encrypt.h @@ -0,0 +1,147 @@ +/* + * encrypt.h - Define the enryptor's interface + * + * Copyright (C) 2013 - 2015, Max Lv + * + * This file is part of the shadowsocks-libev. + * + * shadowsocks-libev is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * shadowsocks-libev is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with shadowsocks-libev; see the file COPYING. If not, see + * . + */ + +#ifndef _ENCRYPT_H +#define _ENCRYPT_H + +#ifndef __MINGW32__ +#include +#else + +#ifdef max +#undef max +#endif + +#ifdef min +#undef min +#endif + +#endif + +#include +#include +#include +#include + +#if defined(USE_CRYPTO_OPENSSL) + +#include +#include +#include +#include +#include + +typedef const EVP_CIPHER cipher_kt_t; +typedef EVP_CIPHER_CTX *cipher_evp_t; +typedef const EVP_MD digest_type_t; +#define MAX_KEY_LENGTH EVP_MAX_KEY_LENGTH +#define MAX_IV_LENGTH EVP_MAX_IV_LENGTH +#define MAX_MD_SIZE EVP_MAX_MD_SIZE + +#elif defined(USE_CRYPTO_MBEDTLS) + +#include +#include +#include +#include +#include +#include +#include + +typedef mbedtls_cipher_info_t cipher_kt_t; +typedef mbedtls_cipher_context_t cipher_evp_t; +typedef mbedtls_md_info_t digest_type_t; +#define MAX_KEY_LENGTH 64 +#define MAX_IV_LENGTH MBEDTLS_MAX_IV_LENGTH +#define MAX_MD_SIZE MBEDTLS_MD_MAX_SIZE + +#endif + +typedef struct { +#if defined(USE_CRYPTO_OPENSSL) + EVP_CIPHER_CTX *evp; +#elif defined(USE_CRYPTO_MBEDTLS) + mbedtls_cipher_context_t evp; +#endif + uint8_t iv[MAX_IV_LENGTH]; +} cipher_ctx_t; + +#ifdef HAVE_STDINT_H +#include +#elif HAVE_INTTYPES_H +#include +#endif + +//#define SODIUM_BLOCK_SIZE 64 + +#define NONE -1 +#define TABLE 0 +#define RC4 1 +#define RC4_MD5 2 +#define AES_128_CFB 3 +#define AES_192_CFB 4 +#define AES_256_CFB 5 +#define BF_CFB 6 +#define CAMELLIA_128_CFB 7 +#define CAMELLIA_192_CFB 8 +#define CAMELLIA_256_CFB 9 +#define CAST5_CFB 10 +#define DES_CFB 11 +#define IDEA_CFB 12 +#define RC2_CFB 13 +#define SEED_CFB 14 +#define AES_128_GCM 15 +#define AES_192_GCM 16 +#define AES_256_GCM 17 +#define CHACHA20_IETF_POLY1305 18 + +#define min(a, b) (((a) < (b)) ? (a) : (b)) +#define max(a, b) (((a) > (b)) ? (a) : (b)) + +typedef struct enc_info_t { + int method; + uint8_t key[MAX_KEY_LENGTH]; + int key_len; + int iv_len; + uint8_t *enc_table; + uint8_t *dec_table; +} enc_info; + +struct enc_ctx { + uint8_t init; + uint64_t counter; /* nonce counter for AEAD, incremented per AE op */ + cipher_ctx_t evp; + enc_info * info; + uint8_t subkey[MAX_KEY_LENGTH]; /* AEAD per-session subkey derived from salt */ +}; + +int enc_init(enc_info * info, const char *pass, const char *method); +void enc_free(enc_info * info); +int enc_ctx_init(enc_info * info, struct enc_ctx *ctx, int enc); +void enc_ctx_free(struct enc_ctx *ctx); +int ss_encrypt(struct enc_ctx *ctx, char *plaintext, size_t plen, + char * ciphertext, size_t * clen); +int ss_decrypt(struct enc_ctx *ctx, char *ciphertext, size_t clen, + char *plaintext, size_t *olen); +size_t ss_calc_buffer_size(struct enc_ctx *ctx, size_t ilen); + +#endif // _ENCRYPT_H diff --git a/extra/search.h b/extra/search.h new file mode 100644 index 00000000..9059e6f1 --- /dev/null +++ b/extra/search.h @@ -0,0 +1,62 @@ +#ifndef _SEARCH_H +#define _SEARCH_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#define __NEED_size_t + +typedef enum { FIND, ENTER } ACTION; +typedef enum { preorder, postorder, endorder, leaf } VISIT; + +typedef struct entry { + char *key; + void *data; +} ENTRY; + +int hcreate(size_t); +void hdestroy(void); +ENTRY *hsearch(ENTRY, ACTION); + +#ifdef _GNU_SOURCE +struct hsearch_data { + struct __tab *__tab; + unsigned int __unused1; + unsigned int __unused2; +}; + +int hcreate_r(size_t, struct hsearch_data *); +void hdestroy_r(struct hsearch_data *); +int hsearch_r(ENTRY, ACTION, ENTRY **, struct hsearch_data *); +#endif + +void insque(void *, void *); +void remque(void *); + +void *lsearch(const void *, void *, size_t *, size_t, + int (*)(const void *, const void *)); +void *lfind(const void *, const void *, size_t *, size_t, + int (*)(const void *, const void *)); + +void *tdelete(const void *__restrict, void **__restrict, int(*)(const void *, const void *)); +void *tfind(const void *, void *const *, int(*)(const void *, const void *)); +void *tsearch(const void *, void **, int (*)(const void *, const void *)); +void twalk(const void *, void (*)(const void *, VISIT, int)); + +#ifdef _GNU_SOURCE +struct qelem { + struct qelem *q_forw, *q_back; + char q_data[1]; +}; + +void tdestroy(void *, void (*)(void *)); +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/extra/tsearch_avl.c b/extra/tsearch_avl.c new file mode 100644 index 00000000..104a65b3 --- /dev/null +++ b/extra/tsearch_avl.c @@ -0,0 +1,204 @@ +#include +#include "search.h" + +/* +avl tree implementation using recursive functions +the height of an n node tree is less than 1.44*log2(n+2)-1 +(so the max recursion depth in case of a tree with 2^32 nodes is 45) +*/ + +struct node { + const void *key; + struct node *left; + struct node *right; + int height; +}; + +static int delta(struct node *n) { + return (n->left ? n->left->height:0) - (n->right ? n->right->height:0); +} + +static void updateheight(struct node *n) { + n->height = 0; + if (n->left && n->left->height > n->height) + n->height = n->left->height; + if (n->right && n->right->height > n->height) + n->height = n->right->height; + n->height++; +} + +static struct node *rotl(struct node *n) { + struct node *r = n->right; + n->right = r->left; + r->left = n; + updateheight(n); + updateheight(r); + return r; +} + +static struct node *rotr(struct node *n) { + struct node *l = n->left; + n->left = l->right; + l->right = n; + updateheight(n); + updateheight(l); + return l; +} + +static struct node *balance(struct node *n) { + int d = delta(n); + + if (d < -1) { + if (delta(n->right) > 0) + n->right = rotr(n->right); + return rotl(n); + } else if (d > 1) { + if (delta(n->left) < 0) + n->left = rotl(n->left); + return rotr(n); + } + updateheight(n); + return n; +} + +static struct node *find(struct node *n, const void *k, + int (*cmp)(const void *, const void *)) +{ + int c; + + if (!n) + return 0; + c = cmp(k, n->key); + if (c == 0) + return n; + if (c < 0) + return find(n->left, k, cmp); + else + return find(n->right, k, cmp); +} + +static struct node *insert(struct node *n, const void *k, + int (*cmp)(const void *, const void *), struct node **found) +{ + struct node *r; + int c; + + if (!n) { + n = malloc(sizeof *n); + if (n) { + n->key = k; + n->left = n->right = 0; + n->height = 1; + } + *found = n; + return n; + } + c = cmp(k, n->key); + if (c == 0) { + *found = n; + return 0; + } + r = insert(c < 0 ? n->left : n->right, k, cmp, found); + if (r) { + if (c < 0) + n->left = r; + else + n->right = r; + r = balance(n); + } + return r; +} + +static struct node *remove_rightmost(struct node *n, struct node **rightmost) +{ + if (!n->right) { + *rightmost = n; + return n->left; + } + n->right = remove_rightmost(n->right, rightmost); + return balance(n); +} + +static struct node *remove(struct node **n, const void *k, + int (*cmp)(const void *, const void *), struct node *parent) +{ + int c; + + if (!*n) + return 0; + c = cmp(k, (*n)->key); + if (c == 0) { + struct node *r = *n; + if (r->left) { + r->left = remove_rightmost(r->left, n); + (*n)->left = r->left; + (*n)->right = r->right; + *n = balance(*n); + } else + *n = r->right; + free(r); + return parent; + } + if (c < 0) + parent = remove(&(*n)->left, k, cmp, *n); + else + parent = remove(&(*n)->right, k, cmp, *n); + if (parent) + *n = balance(*n); + return parent; +} + +void *tdelete(const void *restrict key, void **restrict rootp, + int(*compar)(const void *, const void *)) +{ + if (!rootp) + return 0; + struct node *n = *rootp; + struct node *ret; + /* last argument is arbitrary non-null pointer + which is returned when the root node is deleted */ + ret = remove(&n, key, compar, n); + *rootp = n; + return ret; +} + +void *tfind(const void *key, void *const *rootp, + int(*compar)(const void *, const void *)) +{ + if (!rootp) + return 0; + return find(*rootp, key, compar); +} + +void *tsearch(const void *key, void **rootp, + int (*compar)(const void *, const void *)) +{ + struct node *update; + struct node *ret; + if (!rootp) + return 0; + update = insert(*rootp, key, compar, &ret); + if (update) + *rootp = update; + return ret; +} + +static void walk(const struct node *r, void (*action)(const void *, VISIT, int), int d) +{ + if (r == 0) + return; + if (r->left == 0 && r->right == 0) + action(r, leaf, d); + else { + action(r, preorder, d); + walk(r->left, action, d+1); + action(r, postorder, d); + walk(r->right, action, d+1); + action(r, endorder, d); + } +} + +void twalk(const void *root, void (*action)(const void *, VISIT, int)) +{ + walk(root, action, 0); +} diff --git a/http-auth.c b/http-auth.c index d37d6559..c4a67942 100644 --- a/http-auth.c +++ b/http-auth.c @@ -32,15 +32,15 @@ char* basic_authentication_encode(const char *user, const char *passwd) { /* prepare the user:pass key pair */ int pair_len = strlen(user) + 1 + strlen(passwd); - char *pair_ptr = calloc(pair_len + 1, 1); + char pair[pair_len + 1]; - sprintf(pair_ptr, "%s:%s", user, passwd); + sprintf(pair, "%s:%s", user, passwd); /* calculate the final string length */ int basic_len = BASE64_SIZE(pair_len); char *basic_ptr = calloc(basic_len + 1, 1); - if (!base64_encode(basic_ptr, basic_len, (const uint8_t*)pair_ptr, pair_len)) + if (!base64_encode(basic_ptr, basic_len, (const uint8_t*)pair, pair_len)) return NULL; return basic_ptr; @@ -191,26 +191,26 @@ char* digest_authentication_encode(const char *line, const char *user, const cha char response[MD5_HASHLEN * 2 + 1]; /* A1 = username-value ":" realm-value ":" passwd */ - md5_init(&ctx); + md5_init_rs(&ctx); md5_append(&ctx, (md5_byte_t*)user, strlen(user)); md5_append(&ctx, (md5_byte_t*)":", 1); md5_append(&ctx, (md5_byte_t*)realm, strlen(realm)); md5_append(&ctx, (md5_byte_t*)":", 1); md5_append(&ctx, (md5_byte_t*)passwd, strlen(passwd)); - md5_finish(&ctx, hash); + md5_finish_rs(&ctx, hash); dump_hash(a1buf, hash); /* A2 = Method ":" digest-uri-value */ - md5_init(&ctx); + md5_init_rs(&ctx); md5_append(&ctx, (md5_byte_t*)method, strlen(method)); md5_append(&ctx, (md5_byte_t*)":", 1); md5_append(&ctx, (md5_byte_t*)path, strlen(path)); - md5_finish(&ctx, hash); + md5_finish_rs(&ctx, hash); dump_hash(a2buf, hash); /* qop set: request-digest = H(A1) ":" nonce-value ":" nc-value ":" cnonce-value ":" qop-value ":" H(A2) */ /* not set: request-digest = H(A1) ":" nonce-value ":" H(A2) */ - md5_init(&ctx); + md5_init_rs(&ctx); md5_append(&ctx, (md5_byte_t*)a1buf, strlen(a1buf)); md5_append(&ctx, (md5_byte_t*)":", 1); md5_append(&ctx, (md5_byte_t*)nonce, strlen(nonce)); @@ -224,7 +224,7 @@ char* digest_authentication_encode(const char *line, const char *user, const cha md5_append(&ctx, (md5_byte_t*)":", 1); } md5_append(&ctx, (md5_byte_t*)a2buf, strlen(a2buf)); - md5_finish(&ctx, hash); + md5_finish_rs(&ctx, hash); dump_hash(response, hash); /* prepare the final string */ diff --git a/http-connect.c b/http-connect.c index 685ab54c..170e3e85 100644 --- a/http-connect.c +++ b/http-connect.c @@ -40,7 +40,7 @@ typedef enum httpc_state_t { } httpc_state; -#define HTTP_HEAD_WM_HIGH 4096 // that should be enough for one HTTP line. +#define HTTP_HEAD_WM_HIGH 8192 // that should be enough for one HTTP line. static void httpc_client_init(redsocks_client *client) @@ -55,38 +55,24 @@ static void httpc_instance_fini(redsocks_instance *instance) auth->last_auth_query = NULL; } -static struct evbuffer *httpc_mkconnect(redsocks_client *client); - extern const char *auth_request_header; extern const char *auth_response_header; -static char *get_auth_request_header(struct evbuffer *buf) -{ - char *line; - for (;;) { - line = redsocks_evbuffer_readline(buf); - if (line == NULL || *line == '\0' || strchr(line, ':') == NULL) { - free(line); - return NULL; - } - if (strncasecmp(line, auth_request_header, strlen(auth_request_header)) == 0) - return line; - free(line); - } -} +extern char *get_auth_request_header(struct evbuffer *buf); -static void httpc_read_cb(struct bufferevent *buffev, void *_arg) +void httpc_read_cb(struct bufferevent *buffev, void *_arg) { redsocks_client *client = _arg; int dropped = 0; + struct evbuffer * evbinput = bufferevent_get_input(buffev); assert(client->state >= httpc_request_sent); redsocks_touch_client(client); if (client->state == httpc_request_sent) { - size_t len = EVBUFFER_LENGTH(buffev->input); - char *line = redsocks_evbuffer_readline(buffev->input); + size_t len = evbuffer_get_length(evbinput); + char *line = evbuffer_readln(evbinput, NULL, EVBUFFER_EOL_CRLF); if (line) { unsigned int code; if (sscanf(line, "HTTP/%*u.%*u %u", &code) == 1) { // 1 == one _assigned_ match @@ -104,7 +90,7 @@ static void httpc_read_cb(struct bufferevent *buffev, void *_arg) dropped = 1; } else { - char *auth_request = get_auth_request_header(buffev->input); + char *auth_request = get_auth_request_header(evbinput); if (!auth_request) { redsocks_log_error(client, LOG_NOTICE, "403 found, but no proxy auth challenge"); @@ -132,14 +118,18 @@ static void httpc_read_cb(struct bufferevent *buffev, void *_arg) } /* close relay tunnel */ - redsocks_close(EVENT_FD(&client->relay->ev_write)); + int fd = bufferevent_getfd(client->relay); bufferevent_free(client->relay); + redsocks_close(fd); /* set to initial state*/ client->state = httpc_new; /* and reconnect */ - redsocks_connect_relay(client); + if (client->instance->relay_ss->connect_relay) + client->instance->relay_ss->connect_relay(client); + else + redsocks_connect_relay(client); return; } } @@ -163,7 +153,7 @@ static void httpc_read_cb(struct bufferevent *buffev, void *_arg) return; while (client->state == httpc_reply_came) { - char *line = redsocks_evbuffer_readline(buffev->input); + char *line = evbuffer_readln(evbinput, NULL, EVBUFFER_EOL_CRLF); if (line) { if (strlen(line) == 0) { client->state = httpc_headers_skipped; @@ -180,7 +170,7 @@ static void httpc_read_cb(struct bufferevent *buffev, void *_arg) } } -static struct evbuffer *httpc_mkconnect(redsocks_client *client) +struct evbuffer *httpc_mkconnect(redsocks_client *client) { struct evbuffer *buff = NULL, *retval = NULL; int len; @@ -197,6 +187,10 @@ static struct evbuffer *httpc_mkconnect(redsocks_client *client) const char *auth_scheme = NULL; char *auth_string = NULL; + /* calculate uri */ + char uri[RED_INET_ADDRSTRLEN]; + red_inet_ntop(&client->destaddr, uri, sizeof(uri)); + if (auth->last_auth_query != NULL) { /* find previous auth challange */ @@ -204,10 +198,6 @@ static struct evbuffer *httpc_mkconnect(redsocks_client *client) auth_string = basic_authentication_encode(client->instance->config.login, client->instance->config.password); auth_scheme = "Basic"; } else if (strncasecmp(auth->last_auth_query, "Digest", 6) == 0) { - /* calculate uri */ - char uri[128]; - snprintf(uri, 128, "%s:%u", inet_ntoa(client->destaddr.sin_addr), ntohs(client->destaddr.sin_port)); - /* prepare an random string for cnounce */ char cnounce[17]; snprintf(cnounce, sizeof(cnounce), "%08x%08x", red_randui32(), red_randui32()); @@ -220,16 +210,11 @@ static struct evbuffer *httpc_mkconnect(redsocks_client *client) } if (auth_string == NULL) { - len = evbuffer_add_printf(buff, - "CONNECT %s:%u HTTP/1.0\r\n\r\n", - inet_ntoa(client->destaddr.sin_addr), - ntohs(client->destaddr.sin_port) - ); + len = evbuffer_add_printf(buff, "CONNECT %s HTTP/1.0\r\n\r\n", uri); } else { len = evbuffer_add_printf(buff, - "CONNECT %s:%u HTTP/1.0\r\n%s %s %s\r\n\r\n", - inet_ntoa(client->destaddr.sin_addr), - ntohs(client->destaddr.sin_port), + "CONNECT %s HTTP/1.0\r\n%s %s %s\r\n\r\n", + uri, auth_response_header, auth_scheme, auth_string @@ -253,7 +238,7 @@ static struct evbuffer *httpc_mkconnect(redsocks_client *client) } -static void httpc_write_cb(struct bufferevent *buffev, void *_arg) +void httpc_write_cb(struct bufferevent *buffev, void *_arg) { redsocks_client *client = _arg; diff --git a/http-relay.c b/http-relay.c index 51c6f4c2..a1a203c7 100644 --- a/http-relay.c +++ b/http-relay.c @@ -27,12 +27,15 @@ #include #include #include +#include +#include #include "log.h" #include "redsocks.h" #include "http-auth.h" #include "utils.h" #define HTTP_HEAD_WM_HIGH (4096) +#define MAX_HTTP_REQUEST_LINE_LENGTH 4095 typedef enum httpr_state_t { httpr_new, @@ -60,7 +63,7 @@ typedef struct httpr_client_t { extern const char *auth_request_header; extern const char *auth_response_header; -static void httpr_connect_relay(redsocks_client *client); +static int httpr_connect_relay(redsocks_client *client); static int httpr_buffer_init(httpr_buffer *buff) { @@ -126,11 +129,11 @@ static void httpr_instance_fini(redsocks_instance *instance) auth->last_auth_query = NULL; } -static char *get_auth_request_header(struct evbuffer *buf) +char *get_auth_request_header(struct evbuffer *buf) { char *line; for (;;) { - line = redsocks_evbuffer_readline(buf); + line = evbuffer_readln(buf, NULL, EVBUFFER_EOL_CRLF); if (line == NULL || *line == '\0' || strchr(line, ':') == NULL) { free(line); return NULL; @@ -146,6 +149,7 @@ static void httpr_relay_read_cb(struct bufferevent *buffev, void *_arg) redsocks_client *client = _arg; httpr_client *httpr = (void*)(client + 1); int dropped = 0; + struct evbuffer * evbinput = bufferevent_get_input(buffev); assert(client->state >= httpr_request_sent); @@ -155,8 +159,8 @@ static void httpr_relay_read_cb(struct bufferevent *buffev, void *_arg) httpr_buffer_init(&httpr->relay_buffer); if (client->state == httpr_request_sent) { - size_t len = EVBUFFER_LENGTH(buffev->input); - char *line = redsocks_evbuffer_readline(buffev->input); + size_t len = evbuffer_get_length(evbinput); + char *line = evbuffer_readln(evbinput, NULL, EVBUFFER_EOL_CRLF); if (line) { httpr_buffer_append(&httpr->relay_buffer, line, strlen(line)); httpr_buffer_append(&httpr->relay_buffer, "\r\n", 2); @@ -176,14 +180,14 @@ static void httpr_relay_read_cb(struct bufferevent *buffev, void *_arg) dropped = 1; } else { - free(line); - char *auth_request = get_auth_request_header(buffev->input); + char *auth_request = get_auth_request_header(evbinput); if (!auth_request) { redsocks_log_error(client, LOG_NOTICE, "403 found, but no proxy auth challenge"); redsocks_drop_client(client); dropped = 1; } else { + free(line); free(auth->last_auth_query); char *ptr = auth_request; @@ -205,8 +209,9 @@ static void httpr_relay_read_cb(struct bufferevent *buffev, void *_arg) } /* close relay tunnel */ - redsocks_close(EVENT_FD(&client->relay->ev_write)); + int fd = bufferevent_getfd(client->relay); bufferevent_free(client->relay); + redsocks_close(fd); /* set to initial state*/ client->state = httpr_recv_request_headers; @@ -236,7 +241,7 @@ static void httpr_relay_read_cb(struct bufferevent *buffev, void *_arg) return; while (client->state == httpr_reply_came) { - char *line = redsocks_evbuffer_readline(buffev->input); + char *line = evbuffer_readln(evbinput, NULL, EVBUFFER_EOL_CRLF); if (line) { httpr_buffer_append(&httpr->relay_buffer, line, strlen(line)); httpr_buffer_append(&httpr->relay_buffer, "\r\n", 2); @@ -340,6 +345,8 @@ static void httpr_relay_write_cb(struct bufferevent *buffev, void *_arg) len |= bufferevent_write(client->relay, " ", 1); len |= bufferevent_write(client->relay, auth_string, strlen(auth_string)); len |= bufferevent_write(client->relay, "\r\n", 2); + free(auth_string); + auth_string = NULL; if (len) { redsocks_log_errno(client, LOG_ERR, "bufferevent_write"); redsocks_drop_client(client); @@ -347,7 +354,6 @@ static void httpr_relay_write_cb(struct bufferevent *buffev, void *_arg) } } - free(auth_string); len = bufferevent_write(client->relay, httpr->client_buffer.buff, httpr->client_buffer.len); if (len < 0) { @@ -358,8 +364,7 @@ static void httpr_relay_write_cb(struct bufferevent *buffev, void *_arg) client->state = httpr_request_sent; - buffev->wm_read.low = 1; - buffev->wm_read.high = HTTP_HEAD_WM_HIGH; + bufferevent_setwatermark(buffev, EV_READ, 1, HTTP_HEAD_WM_HIGH); bufferevent_enable(buffev, EV_READ); } } @@ -371,23 +376,23 @@ static int httpr_append_header(redsocks_client *client, char *line) if (httpr_buffer_append(&httpr->client_buffer, line, strlen(line)) != 0) return -1; - if (httpr_buffer_append(&httpr->client_buffer, "\x0d\x0a", 2) != 0) + if (httpr_buffer_append(&httpr->client_buffer, "\r\n", 2) != 0) return -1; return 0; } // This function is not reenterable -static char *fmt_http_host(struct sockaddr_in addr) +static const char *fmt_http_host(struct sockaddr_storage * addr) { - static char host[] = "123.123.123.123:12345"; - if (ntohs(addr.sin_port) == 80) - return inet_ntoa(addr.sin_addr); + static char host[RED_INET_ADDRSTRLEN]; + + if ( + (addr->ss_family == AF_INET && ntohs(((struct sockaddr_in *)addr)->sin_port) == 80) + || (addr->ss_family == AF_INET6 && ntohs(((struct sockaddr_in6 *)addr)->sin6_port) == 80) + ) + return inet_ntop(addr->ss_family, addr, &host[0], sizeof(host)); else { - snprintf(host, sizeof(host), - "%s:%u", - inet_ntoa(addr.sin_addr), - ntohs(addr.sin_port) - ); + red_inet_ntop(addr, host, sizeof(host)); return host; } } @@ -396,44 +401,45 @@ static int httpr_toss_http_firstline(redsocks_client *client) { httpr_client *httpr = (void*)(client + 1); char *uri = NULL; - char *host = httpr->has_host ? httpr->host : fmt_http_host(client->destaddr); + const char *host = httpr->has_host ? httpr->host : fmt_http_host(&client->destaddr); + static char nbuff[MAX_HTTP_REQUEST_LINE_LENGTH + 1]; + size_t len = 0; assert(httpr->firstline); + if (strlen(httpr->firstline) + strlen(host) + 7 + 2 > MAX_HTTP_REQUEST_LINE_LENGTH) { + redsocks_log_error(client, LOG_NOTICE, "HTTP request line too line!"); + goto fail; + } + uri = strchr(httpr->firstline, ' '); if (uri) - uri += 1; // one char further + while (*uri == ' ') + uri += 1; // one char further else { redsocks_log_error(client, LOG_NOTICE, "malformed request came"); goto fail; } - httpr_buffer nbuff; - if (httpr_buffer_init(&nbuff) != 0) { - redsocks_log_error(client, LOG_ERR, "httpr_buffer_init"); - goto fail; + memcpy(&nbuff[0], httpr->firstline, uri - httpr->firstline); + len = uri - httpr->firstline; + if (*uri == '/') { + memcpy(&nbuff[len], "http://", 7); + len += 7; + memcpy(&nbuff[len], host, strlen(host)); + len += strlen(host); } - - if (httpr_buffer_append(&nbuff, httpr->firstline, uri - httpr->firstline) != 0) - goto addition_fail; - if (httpr_buffer_append(&nbuff, "http://", 7) != 0) - goto addition_fail; - if (httpr_buffer_append(&nbuff, host, strlen(host)) != 0) - goto addition_fail; - if (httpr_buffer_append(&nbuff, uri, strlen(uri)) != 0) - goto addition_fail; - if (httpr_buffer_append(&nbuff, "\x0d\x0a", 2) != 0) - goto addition_fail; + memcpy(&nbuff[len], uri, strlen(uri)); + len += strlen(uri); + memcpy(&nbuff[len], "\r\n", 3); // Including NULL terminator + len += 3; free(httpr->firstline); - httpr->firstline = calloc(nbuff.len + 1, 1); - strcpy(httpr->firstline, nbuff.buff); - httpr_buffer_fini(&nbuff); + httpr->firstline = calloc(len, 1); + strcpy(httpr->firstline, &nbuff[0]); return 0; -addition_fail: - httpr_buffer_fini(&nbuff); fail: redsocks_log_error(client, LOG_ERR, "httpr_toss_http_firstline"); return -1; @@ -452,7 +458,7 @@ static void httpr_client_read_content(struct bufferevent *buffev, redsocks_clien } int error; while (true) { - error = evbuffer_remove(buffev->input, post_buffer, post_buffer_len); + error = evbuffer_remove(bufferevent_get_input(buffev), post_buffer, post_buffer_len); if (error < 0) { free(post_buffer); redsocks_log_error(client, LOG_ERR, "evbuffer_remove"); @@ -490,7 +496,7 @@ static void httpr_client_read_cb(struct bufferevent *buffev, void *_arg) char *line = NULL; int connect_relay = 0; - while (!connect_relay && (line = redsocks_evbuffer_readline(buffev->input))) { + while (!connect_relay && (line = evbuffer_readln(bufferevent_get_input(buffev), NULL, EVBUFFER_EOL_CRLF))) { int skip_line = 0; int do_drop = 0; @@ -517,9 +523,9 @@ static void httpr_client_read_cb(struct bufferevent *buffev, void *_arg) do_drop = 1; if (!httpr->has_host) { - char host[32]; // "Host: 123.456.789.012:34567" + char host[266]; // "Host: 123.456.789.012:34567" int written_wo_null = snprintf(host, sizeof(host), "Host: %s", - fmt_http_host(client->destaddr)); + fmt_http_host(&client->destaddr)); UNUSED(written_wo_null); assert(0 < written_wo_null && written_wo_null < sizeof(host)); if (httpr_append_header(client, host) < 0) @@ -554,16 +560,17 @@ static void httpr_client_read_cb(struct bufferevent *buffev, void *_arg) } } -static void httpr_connect_relay(redsocks_client *client) +static int httpr_connect_relay(redsocks_client *client) { int error; - client->client->readcb = httpr_client_read_cb; + replace_readcb(client->client, httpr_client_read_cb); error = bufferevent_enable(client->client, EV_READ); if (error) { redsocks_log_errno(client, LOG_ERR, "bufferevent_enable"); redsocks_drop_client(client); } + return error; } relay_subsys http_relay_subsys = diff --git a/https-connect.c b/https-connect.c new file mode 100644 index 00000000..dbbd73cf --- /dev/null +++ b/https-connect.c @@ -0,0 +1,265 @@ +/* redsocks2 - transparent TCP-to-proxy redirector + * Copyright (C) 2013-2017 Zhuofei Wang + * + * This code is based on redsocks project developed by Leonid Evdokimov. + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "log.h" +#include "redsocks.h" +#include "http-auth.h" + +#if LIBEVENT_VERSION_NUMBER >= 0x02010100 +# ifndef EVENT__HAVE_OPENSSL +# error The libevent2 you are compiling with does not have OpenSSL enabled! +# endif +#else +# ifndef _EVENT_HAVE_OPENSSL +# error The libevent2 you are compiling with does not have OpenSSL enabled! +# endif +#endif + +typedef enum httpsc_state_t { + httpc_new, + httpc_request_sent, + httpc_reply_came, + httpc_headers_skipped, + httpc_MAX, +} httpc_state; + +typedef struct httpsc_client_t { + SSL * ssl; +} httpsc_client; + +typedef struct httpsc_instance_t { + http_auth auth; + SSL_CTX * ctx; +} httpsc_instance; + +#define HTTP_HEAD_WM_HIGH 8192 // that should be enough for one HTTP line. + + +static void log_ssl_error(redsocks_client *client, struct bufferevent * buffev) +{ + unsigned long err; + while ((err = (bufferevent_get_openssl_error(buffev)))) { + const char *msg = (const char*) + ERR_reason_error_string(err); + const char *lib = (const char*) + ERR_lib_error_string(err); + const char *func = (const char*) + ERR_func_error_string(err); + redsocks_log_errno(client, LOG_DEBUG, "SSL Error: %s %s: %s", lib, func, msg); + } +} + +static void httpsc_client_init(redsocks_client *client) +{ + client->state = httpc_new; +} + +static void httpsc_client_fini(redsocks_client *client) +{ + httpsc_client *sclient = (void*)(client + 1); + struct bufferevent * underlying = NULL; + + if (client->relay) { + underlying = bufferevent_get_underlying(client->relay); + if (underlying) { + bufferevent_free(client->relay); + client->relay = underlying; + } + } + if (sclient->ssl) { + SSL_free(sclient->ssl); + sclient->ssl = NULL; + } +} + +static int httpsc_instance_init(struct redsocks_instance_t *instance) +{ + httpsc_instance * httpsc = (httpsc_instance *)(instance + 1); + SSL_CTX * ctx = NULL; + + #if (OPENSSL_VERSION_NUMBER < 0x10100000L) + ctx = SSL_CTX_new(SSLv23_client_method()); + #else + ctx = SSL_CTX_new(TLS_client_method()); + #endif + if (!ctx) + { + unsigned long err = ERR_get_error(); + log_error(LOG_ERR, "Failed to allocate SSL context. SSL Error: %s", ERR_lib_error_string(err)); + return -1; + } + httpsc->ctx = ctx; + return 0; +} + +static void httpsc_instance_fini(redsocks_instance *instance) +{ + httpsc_instance * httpsc = (httpsc_instance *)(instance + 1); + + free(httpsc->auth.last_auth_query); + httpsc->auth.last_auth_query = NULL; + if (httpsc->ctx) { + SSL_CTX_free (httpsc->ctx); + httpsc->ctx = NULL; + } +} + +extern struct evbuffer *httpc_mkconnect(redsocks_client *client); +extern void httpc_read_cb(struct bufferevent *buffev, void *_arg); + +static void httpsc_event_cb(struct bufferevent *buffev, short what, void *_arg) +{ + redsocks_client *client = _arg; + assert(buffev == client->relay || buffev == client->client); + + redsocks_touch_client(client); + + if (!(what & BEV_EVENT_ERROR)) + errno = red_socket_geterrno(buffev); +#if LIBEVENT_VERSION_NUMBER >= 0x02010100 + else if (!bufferevent_openssl_get_allow_dirty_shutdown(client->relay)) +#else + else +#endif + log_ssl_error(client, client->relay); + redsocks_log_errno(client, LOG_DEBUG, "%s, what: " event_fmt_str, + buffev == client->client?"client":"relay", + event_fmt(what)); + + if (what == (BEV_EVENT_READING|BEV_EVENT_EOF)) { + redsocks_shutdown(client, buffev, SHUT_RD, 1); + // Ensure the other party could send remaining data and SHUT_WR also + if (buffev == client->client) + { + if (!(client->relay_evshut & EV_WRITE) && client->relay_connected) + // when we got EOF from client, we need to shutdown relay's write + process_shutdown_on_write_(client, client->client, client->relay); + } + else + { +#if LIBEVENT_VERSION_NUMBER >= 0x02010100 + if (bufferevent_openssl_get_allow_dirty_shutdown(client->relay)) +#endif + log_ssl_error(client, client->relay); + if (!(client->client_evshut & EV_WRITE)) + bufferevent_enable(client->client, EV_WRITE); + } + } + else if (what == BEV_EVENT_CONNECTED) { + // usually this event is not generated as 'connect' is used to + // setup connection. For openssl socket, this event is generated. + client->relay_connected = 1; + /* We do not need to detect timeouts any more. + The two peers will handle it. */ + bufferevent_set_timeouts(client->relay, NULL, NULL); + redsocks_write_helper_ex( + buffev, client, + httpc_mkconnect, httpc_request_sent, 0, HTTP_HEAD_WM_HIGH + ); + } + else { + redsocks_drop_client(client); + } +} + +static void httpsc_read_cb(struct bufferevent *buffev, void *_arg) +{ + redsocks_client *client = _arg; + + httpc_read_cb(buffev, _arg); + + if (client->state == httpc_headers_skipped) { + bufferevent_data_cb read_cb, write_cb; + + replace_eventcb(client->client, httpsc_event_cb); + struct evbuffer * input = bufferevent_get_input(client->client); + if (evbuffer_get_length(input)) +#if LIBEVENT_VERSION_NUMBER >= 0x02010100 + bufferevent_trigger(client->relay, EV_WRITE, 0); +#else + if (client->relay->writecb) + client->relay->writecb(client->relay, client); +#endif + } +} + +static void httpsc_write_cb(struct bufferevent *buffev, void *_arg) +{ + redsocks_client *client = _arg; + struct bufferevent * from = client->client; + struct bufferevent * to = client->relay; + + process_shutdown_on_write_(client, from, to); +} + +static int httpsc_connect_relay(redsocks_client *client) +{ + httpsc_client *sclient = (void*)(client + 1); + httpsc_instance *httpsc = (httpsc_instance *)(client->instance + 1); + char * interface = client->instance->config.interface; + struct timeval tv = {client->instance->config.timeout, 0}; + + if (!sclient->ssl) + sclient->ssl = SSL_new(httpsc->ctx); + + // Allowing binding relay socket to specified IP for outgoing connections + client->relay = red_connect_relay_ssl(interface, &client->instance->config.relayaddr, + sclient->ssl, + httpsc_read_cb, + NULL, + httpsc_event_cb, client, &tv); + if (!client->relay) { + redsocks_log_errno(client, LOG_ERR, "red_connect_relay_ssl"); + redsocks_drop_client(client); + return -1; + } + + return 0; +} + +relay_subsys https_connect_subsys = +{ + .name = "https-connect", + .payload_len = sizeof(httpsc_client), + .instance_payload_len = sizeof(httpsc_instance), + .readcb = httpsc_read_cb, + .writecb = httpsc_write_cb, + .init = httpsc_client_init, + .fini = httpsc_client_fini, + .connect_relay = httpsc_connect_relay, + .instance_init = httpsc_instance_init, + .instance_fini = httpsc_instance_fini, +}; + +/* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ +/* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ diff --git a/ipcache.c b/ipcache.c new file mode 100644 index 00000000..48066be2 --- /dev/null +++ b/ipcache.c @@ -0,0 +1,455 @@ +/* redsocks2 - transparent TCP-to-proxy redirector + * Copyright (C) 2013-2017 Zhuofei Wang + * + * This code is based on redsocks project developed by Leonid Evdokimov. + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + + + +#include +#include +#include +#include +#include +#include +#include "redsocks.h" +#include "log.h" +#include "parser.h" +#include "main.h" +#include "ipcache.h" + +#define ADDR_CACHE_BLOCKS 256 +#define ADDR_CACHE_BLOCK_SIZE 16 +#define ADDR_PORT_CHECK 1 +#define CACHE_ITEM_STALE_SECONDS 60*30 +#define MAX_BLOCK_SIZE 32 +#define CACHE_FILE_UPDATE_INTERVAL 3600 * 2 + + +//---------------------------------------------------------------------------------------- +typedef struct cache_config_t { + // Values to be read from config file + uint16_t cache_size; + uint16_t port_check; + uint32_t stale_time; + char * cache_file; + uint32_t autosave_interval; + // Dynamically calculated values. + unsigned int block_size; + unsigned int block_count; +} cache_config; + +static cache_config default_config = { + .cache_size = ADDR_CACHE_BLOCKS * ADDR_CACHE_BLOCK_SIZE / 1024, + .port_check = ADDR_PORT_CHECK, + .stale_time = CACHE_ITEM_STALE_SECONDS, + .autosave_interval = CACHE_FILE_UPDATE_INTERVAL, + .block_size = ADDR_CACHE_BLOCK_SIZE, + .block_count = ADDR_CACHE_BLOCKS, +}; + +static parser_entry cache_entries[] = +{ + { .key = "cache_size", .type = pt_uint16 }, + { .key = "port_check", .type = pt_uint16 }, + { .key = "stale_time", .type = pt_uint32 }, + { .key = "cache_file", .type = pt_pchar }, + { .key = "autosave_interval", .type = pt_uint32 }, + { } +}; + +static int cache_onenter(parser_section *section) +{ + cache_config * config = &default_config; + + config->cache_size = 0; + for (parser_entry *entry = §ion->entries[0]; entry->key; entry++) + entry->addr = + (strcmp(entry->key, "cache_size") == 0) ? (void*)&config->cache_size: + (strcmp(entry->key, "port_check") == 0) ? (void*)&config->port_check: + (strcmp(entry->key, "stale_time") == 0) ? (void*)&config->stale_time: + (strcmp(entry->key, "cache_file") == 0) ? (void*)&config->cache_file: + (strcmp(entry->key, "autosave_interval") == 0) ? (void*)&config->autosave_interval: + NULL; + section->data = config; + return 0; +} + +static int cache_onexit(parser_section *section) +{ + const char *err = NULL; + cache_config * config = section->data; + + section->data = NULL; + for (parser_entry *entry = §ion->entries[0]; entry->key; entry++) + entry->addr = NULL; + + /* Check and update values here */ + /* + Let uer specify cache size by number of 1K items. + Make it easier for user to config cache size. + */ + if (config->cache_size > MAX_BLOCK_SIZE) + err = "Cache size must be in range [0-32]. 0 means default. Default: 4"; + else if (config->cache_size) + { + config->block_count = ADDR_CACHE_BLOCKS; + config->block_size = config->cache_size * 1024 / config->block_count; + while(config->block_size > MAX_BLOCK_SIZE) + { + config->block_count <<= 1; + config->block_size = config->cache_size * 1024 / config->block_count; + } + } + + if (!err && config->stale_time < 5) + err = "Time to stale cache item must be equal or greater than 5 seconds."; + + if (err) + parser_error(section->context, "%s", err); + + return err ? -1 : 0; +} + +static parser_section cache_conf_section = +{ + .name = "ipcache", + .entries = cache_entries, + .onenter = cache_onenter, + .onexit = cache_onexit +}; + +#define block_from_sockaddr_in(addr) (addr->sin_addr.s_addr & (config->block_count -1)) +#define set_cache_changed(changed) (cache_changed = changed) +#define is_cache_changed(changed) (cache_changed != 0) + +typedef struct cache_data_t { + char present; + struct sockaddr_in addr; + time_t access_time; +} cache_data; + +static int * addr_cache_counters = NULL; +static int * addr_cache_pointers = NULL; +static cache_data * addr_cache = NULL; +static char cache_changed = 0; +static struct event * timer_event = NULL; + +static inline cache_data * get_cache_data(unsigned int block, unsigned int index); + +static inline cache_config * get_config() +{ + return &default_config; +} + +static int load_cache(const char * path) +{ + FILE * f; + char line[256]; + char * pline = 0; + // TODO: IPv6 Support + struct sockaddr_in addr; + int addr_size; + + if (!path) + return -1; + + f = fopen(path, "r"); + if (!f) + return -1; + while(1) + { + pline = fgets(line, sizeof(line), f); + if (!pline) + break; + addr_size = sizeof(addr); + if (evutil_parse_sockaddr_port(pline, (struct sockaddr *)&addr, &addr_size)) + log_error(LOG_INFO, "Invalid IP address: %s", line); + else + cache_add_addr(&addr); + } + fclose(f); + return 0; +} + +static int save_cache(const char * path) +{ + FILE * f; + unsigned int blk = 0; + unsigned int idx = 0; + char addr_str[RED_INET_ADDRSTRLEN]; + cache_data * item; + cache_config * config = get_config(); + + + if (!path) + return -1; + + f = fopen(path, "w"); + if (!f) + return -1; + + for (; blk < config->block_count; blk++) + { + for (idx=0; idx < config->block_size; idx++) + { + item = get_cache_data(blk, idx); + if (item && item->present) + { + red_inet_ntop((struct sockaddr_storage *)&item->addr, addr_str, sizeof(addr_str)); + fprintf(f, "%s\n", addr_str); + } + } + } + fclose(f); + return 0; +} + +static void cache_auto_saver(int sig, short what, void *_arg) +{ + cache_config * config = get_config(); + if (is_cache_changed() && config->cache_file) + { + save_cache(config->cache_file); + set_cache_changed(0); + } +} + +static int cache_init() +{ + cache_config * config = get_config(); + size_t size; + struct timeval tv; + + size = sizeof(cache_data) * config->block_size * config->block_count; + if (!addr_cache) + { + addr_cache = malloc(size); + } + memset((void *)addr_cache, 0, size); + + size = sizeof(* addr_cache_counters) * config->block_count; + if (!addr_cache_counters) + { + addr_cache_counters = malloc(size); + } + memset((void *)addr_cache_counters, 0, size); + + size = sizeof(* addr_cache_pointers) * config->block_count; + if (!addr_cache_pointers) + { + addr_cache_pointers = malloc(size); + } + memset((void *)addr_cache_pointers, 0, size); + + if (config->cache_file) + { + if (load_cache(config->cache_file)) + log_error(LOG_INFO, "Failed to load IP addresses from cache file: %s", config->cache_file); + + // start timer to save cache into file periodically. + if (config->autosave_interval) + { + tv.tv_sec = config->autosave_interval; + tv.tv_usec = 0; + timer_event = event_new(get_event_base(), -1, EV_TIMEOUT|EV_PERSIST, cache_auto_saver, NULL); + if (timer_event) + evtimer_add(timer_event, &tv); + } + } + set_cache_changed(0); + + return 0; +} + +static int cache_fini() +{ + cache_config * config = get_config(); + // Update cache file before exit + if (config->autosave_interval && is_cache_changed() && config->cache_file) + { + save_cache(config->cache_file); + set_cache_changed(0); + } + if (timer_event) + { + evtimer_del(timer_event); + event_free(timer_event); + timer_event = NULL; + } + // Free buffers allocated for cache + if (addr_cache) + { + free(addr_cache); + addr_cache = NULL; + } + if (addr_cache_counters) + { + free(addr_cache_counters); + addr_cache_counters = NULL; + } + if (addr_cache_pointers) + { + free(addr_cache_pointers); + addr_cache_pointers = NULL; + } + return 0; +} + +static inline cache_data * get_cache_data(unsigned int block, unsigned int index) +{ + cache_config * config = get_config(); + + unsigned int i = block * config->block_size + index % config->block_size; + return &addr_cache[i]; +} + +static cache_data * get_cache_item(const struct sockaddr_in * addr) +{ + cache_config * config = get_config(); + time_t now = redsocks_time(NULL); + cache_data * item; + /* get block index */ + unsigned int block = block_from_sockaddr_in(addr); + unsigned int count = addr_cache_counters[block]; + unsigned int first = addr_cache_pointers[block]; + unsigned int i = 0; + /* do reverse search for efficency */ + for (i = count; i > 0; i--) + { + item = get_cache_data(block, first+i-1); + if (item + && item->present + && 0 == evutil_sockaddr_cmp((const struct sockaddr *)addr, + (const struct sockaddr *)&item->addr, + config->port_check)) + { + // Remove stale item + if (config->stale_time > 0 + && item->access_time + config->stale_time < now) + { + item->present = 0; + set_cache_changed(1); + return NULL; + } + return item; + } + } + return NULL; +} + +time_t * cache_get_addr_time(const struct sockaddr_in * addr) +{ + cache_data * item = get_cache_item(addr); + if (item) + return &item->access_time; + return NULL; +} + +void cache_touch_addr(const struct sockaddr_in * addr) +{ + cache_data * item = get_cache_item(addr); + if (item) + item->access_time = redsocks_time(NULL); +} + +void cache_add_addr(const struct sockaddr_in * addr) +{ + cache_config * config = get_config(); + cache_data * item; + unsigned int block = block_from_sockaddr_in(addr); + unsigned int count = addr_cache_counters[block]; + /* use 'first' to index item in cache block when count is equal or greater than block size */ + unsigned int first = addr_cache_pointers[block]; + + if (count < config->block_size) { + item = get_cache_data(block, count); + addr_cache_counters[block]++; + } + else { + item = get_cache_data(block, first); + addr_cache_pointers[block]++; + addr_cache_pointers[block] %= config->block_size; + } + memcpy((void *)&item->addr, (void *)addr, sizeof(struct sockaddr_in)); + item->present = 1; + item->access_time = redsocks_time(NULL); + + set_cache_changed(1); +} + +void cache_del_addr(const struct sockaddr_in * addr) +{ + cache_data * item = get_cache_item(addr); + if (item) + { + item->present = 0; + set_cache_changed(1); + } +} + +#define ADDR_COUNT_PER_LINE 4 +static void cache_dumper() +{ + unsigned int count = 0; + unsigned int blk = 0; + unsigned int idx = 0; + unsigned int p = 0, j; + char addr_str[ADDR_COUNT_PER_LINE][RED_INET_ADDRSTRLEN]; + cache_data * item; + cache_config * config = get_config(); + + log_error(LOG_INFO, "Dumping IP cache:"); + for (; blk < config->block_count; blk++) + { + for (idx=0; idx < config->block_size; idx++) + { + item = get_cache_data(blk, idx); + if (item && item->present) + { + count++; + red_inet_ntop((struct sockaddr_storage *)&item->addr, addr_str[p], sizeof(addr_str[0])); + p++; + if (p == ADDR_COUNT_PER_LINE) + { + p = 0; + // TODO: Replace this implementation with better one + log_error(LOG_INFO, "%s %s %s %s", addr_str[0], + addr_str[1], addr_str[2], addr_str[3]); + } + } + } + } + if (p) + { + // TODO: Replace this implementation with better one + for (j = p; j < ADDR_COUNT_PER_LINE; j++) + addr_str[j][0] = 0; + log_error(LOG_INFO, "%s %s %s %s", addr_str[0], + addr_str[1], addr_str[2], addr_str[3]); + } + + log_error(LOG_INFO, "End of dumping IP cache. Totally %u entries.", count); +} + +app_subsys cache_app_subsys = +{ + .init = cache_init, + .fini = cache_fini, + .dump = cache_dumper, + .conf_section = &cache_conf_section, +}; + +/* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ +/* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ diff --git a/ipcache.h b/ipcache.h new file mode 100644 index 00000000..01810189 --- /dev/null +++ b/ipcache.h @@ -0,0 +1,10 @@ +#ifndef REDSOCKS_CACHE_H +#define REDSOCKS_CACHE_H + +void cache_add_addr(const struct sockaddr_in * addr); +void cache_del_addr(const struct sockaddr_in * addr); +void cache_touch_addr(const struct sockaddr_in * addr); +time_t * cache_get_addr_time(const struct sockaddr_in * addr); + +#endif + diff --git a/libc-compat.h b/libc-compat.h index adcf63bc..02b28390 100644 --- a/libc-compat.h +++ b/libc-compat.h @@ -17,9 +17,35 @@ # define IP_RECVORIGDSTADDR IP_ORIGDSTADDR #endif +#ifndef IPV6_ORIGDSTADDR +# warning Using hardcoded value for IPV6_ORIGDSTADDR as libc headers do not define it. +# define IPV6_ORIGDSTADDR 74 +#endif + +#ifndef IPV6_RECVORIGDSTADDR +# warning Using hardcoded value for IPV6_RECVORIGDSTADDR as libc headers do not define it. +# define IPV6_RECVORIGDSTADDR IPV6_ORIGDSTADDR +#endif + #ifndef IP_TRANSPARENT # warning Using hardcoded value for IP_TRANSPARENT as libc headers do not define it. # define IP_TRANSPARENT 19 #endif +#ifndef IPV6_TRANSPARENT +# warning Using hardcoded value for IPV6_TRANSPARENT as libc headers do not define it. +# define IPV6_TRANSPARENT 75 +#endif + +#ifndef SOL_IP +# warning Using hardcoded value for SOL_IP as libc headers do not define it. +# define SOL_IP IPPROTO_IP +#endif + +#if defined __FreeBSD__ || defined __OpenBSD__ || defined __NetBSD__ +#ifndef INADDR_LOOPBACK +# warning Using hardcoded value for INADDR_LOOPBACK for FreeBSD. +# define INADDR_LOOPBACK 0x7F000001 +#endif +#endif #endif // 67C91670_FCCB_4855_BDF7_609F1EECB8B4 diff --git a/libevent-compat.h b/libevent-compat.h deleted file mode 100644 index a7f1ca1b..00000000 --- a/libevent-compat.h +++ /dev/null @@ -1,11 +0,0 @@ -#ifndef UUID_FC148CFA_5ECC_488E_8A62_CD39406C9F1E -#define UUID_FC148CFA_5ECC_488E_8A62_CD39406C9F1E - -/* evutil_socket_t is macros in libevent-2.0, not typedef, libevent-1.4 is - * still supported because of Ubuntu 10.04 LTS */ -#ifndef evutil_socket_t -# warning Using hardcoded value for evutil_socket_t as libevent headers do not define it. -# define evutil_socket_t int -#endif - -#endif // FC148CFA_5ECC_488E_8A62_CD39406C9F1E diff --git a/log.c b/log.c index 670eb3f8..53a638d5 100644 --- a/log.c +++ b/log.c @@ -19,12 +19,10 @@ #include #include #include -#include +#include #include "utils.h" #include "log.h" -const char *error_lowmem = ""; - typedef void (*log_func)(const char *file, int line, const char *func, int priority, const char *message, const char *appendix); static const char* getprioname(int priority) @@ -81,29 +79,22 @@ static void syslog_msg(const char *file, int line, const char *func, int priorit static log_func log_msg = stderr_msg; static log_func log_msg_next = NULL; -static bool should_log_info = true; -static bool should_log_debug = false; - -static bool should_log(int priority) -{ - return (priority != LOG_DEBUG && priority != LOG_INFO) - || (priority == LOG_DEBUG && should_log_debug) - || (priority == LOG_INFO && should_log_info); -} +static int log_mask = LOG_MASK(LOG_NOTICE)|LOG_MASK(LOG_WARNING)|LOG_MASK(LOG_ERR); int log_preopen(const char *dst, bool log_debug, bool log_info) { const char *syslog_prefix = "syslog:"; const char *file_prefix = "file:"; - should_log_debug = log_debug; - should_log_info = log_info; + if (log_debug) + log_mask |= LOG_MASK(LOG_DEBUG); + if (log_info) + log_mask |= LOG_MASK(LOG_INFO); if (strcmp(dst, "stderr") == 0) { log_msg_next = stderr_msg; } else if (strncmp(dst, syslog_prefix, strlen(syslog_prefix)) == 0) { const char *facility_name = dst + strlen(syslog_prefix); int facility = -1; - int logmask; struct { char *name; int value; } *ptpl, tpl[] = { @@ -130,12 +121,12 @@ int log_preopen(const char *dst, bool log_debug, bool log_info) openlog("redsocks", LOG_NDELAY | LOG_PID, facility); - logmask = setlogmask(0); + log_mask = setlogmask(0); if (!log_debug) - logmask &= ~(LOG_MASK(LOG_DEBUG)); + log_mask &= ~(LOG_MASK(LOG_DEBUG)); if (!log_info) - logmask &= ~(LOG_MASK(LOG_INFO)); - setlogmask(logmask); + log_mask &= ~(LOG_MASK(LOG_INFO)); + setlogmask(log_mask); log_msg_next = syslog_msg; } @@ -161,33 +152,24 @@ void log_open() log_msg_next = NULL; } -void _log_vwrite(const char *file, int line, const char *func, int do_errno, int priority, const char *fmt, va_list ap) +int log_level_enabled(int priority) { - if (!should_log(priority)) - return; + return (log_mask & LOG_MASK(priority)); +} +void _log_vwrite(const char *file, int line, const char *func, int do_errno, int priority, const char *fmt, va_list ap) +{ int saved_errno = errno; - struct evbuffer *buff = evbuffer_new(); - const char *message; - - if (buff) { - evbuffer_add_vprintf(buff, fmt, ap); - message = (const char*)EVBUFFER_DATA(buff); - } - else - message = error_lowmem; + char message[MAX_LOG_LENGTH+1]; - log_msg(file, line, func, priority, message, do_errno ? strerror(saved_errno) : NULL); - - if (buff) - evbuffer_free(buff); + if (!log_level_enabled(priority)) + return; + vsnprintf(&message[0], sizeof(message), fmt, ap); + log_msg(file, line, func, priority, &message[0], do_errno ? strerror(saved_errno) : NULL); } void _log_write(const char *file, int line, const char *func, int do_errno, int priority, const char *fmt, ...) { - if (!should_log(priority)) - return; - va_list ap; va_start(ap, fmt); diff --git a/log.h b/log.h index 63308d21..a433b0b0 100644 --- a/log.h +++ b/log.h @@ -5,13 +5,14 @@ #include #include +#define MAX_LOG_LENGTH 512 + #define log_errno(prio, msg...) _log_write(__FILE__, __LINE__, __func__, 1, prio, ## msg) #define log_error(prio, msg...) _log_write(__FILE__, __LINE__, __func__, 0, prio, ## msg) -extern const char *error_lowmem; - int log_preopen(const char *dst, bool log_debug, bool log_info); void log_open(); +int log_level_enabled(int priority); void _log_vwrite(const char *file, int line, const char *func, int do_errno, int priority, const char *fmt, va_list ap); diff --git a/main.c b/main.c index 9d402971..ad439391 100644 --- a/main.c +++ b/main.c @@ -21,7 +21,11 @@ #include #include #include -#include +#include +#if defined(ENABLE_HTTPS_PROXY) +#include +#include +#endif #include "log.h" #include "main.h" #include "utils.h" @@ -30,146 +34,257 @@ extern app_subsys redsocks_subsys; extern app_subsys base_subsys; extern app_subsys redudp_subsys; -extern app_subsys dnstc_subsys; +extern app_subsys tcpdns_subsys; +extern app_subsys autoproxy_app_subsys; +extern app_subsys cache_app_subsys; app_subsys *subsystems[] = { - &redsocks_subsys, - &base_subsys, - &redudp_subsys, - &dnstc_subsys, + &base_subsys, + &redsocks_subsys, + &autoproxy_app_subsys, + &cache_app_subsys, + &redudp_subsys, + &tcpdns_subsys, }; static const char *confname = "redsocks.conf"; static const char *pidfile = NULL; +static struct event_base * g_event_base = NULL; static void terminate(int sig, short what, void *_arg) { - if (event_loopbreak() != 0) - log_error(LOG_WARNING, "event_loopbreak"); + if (g_event_base && event_base_loopbreak(g_event_base) != 0) + log_error(LOG_WARNING, "event_loopbreak"); +} + +static void dump_handler(int sig, short what, void *_arg) +{ + app_subsys **ss; + FOREACH(ss, subsystems) { + if ((*ss)->dump) { + (*ss)->dump(); + } + } +} + +/* Setup signals not to be handled with libevent */ +static int setup_signals() +{ + struct sigaction sa/* , sa_old*/; + + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = SIG_IGN; + if (sigaction(SIGPIPE, &sa, NULL) == -1) { + log_errno(LOG_ERR, "sigaction"); + return -1; + } + return 0; +} + +struct event_base * get_event_base() +{ + return g_event_base; +} + +static void wait_for_network() +{ + struct evutil_addrinfo hints; + struct evutil_addrinfo *answer = NULL; + int err; + + /* Build the hints to tell getaddrinfo how to act. */ + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; /* v4 or v6 is fine. */ + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; /* We want a TCP socket */ + /* Only return addresses we can use. */ + hints.ai_flags = EVUTIL_AI_ADDRCONFIG; + + /* Look up the hostname. */ + do { + err = evutil_getaddrinfo("www.google.com", NULL, &hints, &answer); + if (err) + sleep(2); + /* If there was no error, we should have at least one answer. */ + if (answer) { + evutil_freeaddrinfo(answer); + answer = NULL; + } + } while (err != 0); } int main(int argc, char **argv) { - int error; - app_subsys **ss; - int exit_signals[2] = {SIGTERM, SIGINT}; - struct event terminators[2]; - bool conftest = false; - int opt; - int i; - - evutil_secure_rng_init(); - while ((opt = getopt(argc, argv, "h?vtc:p:")) != -1) { - switch (opt) { - case 't': - conftest = true; - break; - case 'c': - confname = optarg; - break; - case 'p': - pidfile = optarg; - break; - case 'v': - puts(redsocks_version); - printf("Built with libevent-%s\n", LIBEVENT_VERSION); - printf("Runs with libevent-%s\n", event_get_version()); - if (LIBEVENT_VERSION_NUMBER != event_get_version_number()) { - printf("Warning: libevent version number mismatch.\n" - " Headers: %8x\n" - " Runtime: %8x\n", LIBEVENT_VERSION_NUMBER, event_get_version_number()); - } - return EXIT_SUCCESS; - default: - printf( - "Usage: %s [-?hvt] [-c config] [-p pidfile]\n" - " -h, -? this message\n" - " -v print version\n" - " -t test config syntax\n" - " -p write pid to pidfile\n", - argv[0]); - return (opt == '?' || opt == 'h') ? EXIT_SUCCESS : EXIT_FAILURE; - } - } - - - FILE *f = fopen(confname, "r"); - if (!f) { - perror("Unable to open config file"); - return EXIT_FAILURE; - } - - parser_context* parser = parser_start(f); - if (!parser) { - perror("Not enough memory for parser"); - return EXIT_FAILURE; - } - - FOREACH(ss, subsystems) - if ((*ss)->conf_section) - parser_add_section(parser, (*ss)->conf_section); - error = parser_run(parser); - parser_stop(parser); - fclose(f); - - if (error) - return EXIT_FAILURE; - - if (conftest) - return EXIT_SUCCESS; - - event_init(); - memset(terminators, 0, sizeof(terminators)); - - FOREACH(ss, subsystems) { - if ((*ss)->init) { - error = (*ss)->init(); - if (error) - goto shutdown; - } - } - - if (pidfile) { - f = fopen(pidfile, "w"); - if (!f) { - perror("Unable to open pidfile for write"); - return EXIT_FAILURE; - } - fprintf(f, "%d\n", getpid()); - fclose(f); - } - - assert(SIZEOF_ARRAY(exit_signals) == SIZEOF_ARRAY(terminators)); - for (i = 0; i < SIZEOF_ARRAY(exit_signals); i++) { - signal_set(&terminators[i], exit_signals[i], terminate, NULL); - if (signal_add(&terminators[i], NULL) != 0) { - log_errno(LOG_ERR, "signal_add"); - goto shutdown; - } - } - - log_error(LOG_NOTICE, "redsocks started"); - - event_dispatch(); - - log_error(LOG_NOTICE, "redsocks goes down"); + int error; + app_subsys **ss; + int exit_signals[2] = {SIGTERM, SIGINT}; + struct event * terminators[2]; + struct event * dumper = NULL; + bool conftest = false; + int opt; + int i; + bool wait = false; + + evutil_secure_rng_init(); + while ((opt = getopt(argc, argv, "h?wvtc:p:")) != -1) { + switch (opt) { + case 't': + conftest = true; + break; + case 'w': + wait = true; + break; + case 'c': + confname = optarg; + break; + case 'p': + pidfile = optarg; + break; + case 'v': + puts(redsocks_version); + printf("Built with libevent-%s\n", LIBEVENT_VERSION); + printf("Runs with libevent-%s\n", event_get_version()); + if (LIBEVENT_VERSION_NUMBER != event_get_version_number()) { + printf("Warning: libevent version number mismatch.\n" + " Headers: %8x\n" + " Runtime: %8x\n", LIBEVENT_VERSION_NUMBER, event_get_version_number()); + } + return EXIT_SUCCESS; + default: + printf( + "Usage: %s [-?hwvt] [-c config] [-p pidfile]\n" + " -h, -? this message\n" + " -w wait util network ready\n" + " -v print version\n" + " -t test config syntax\n" + " -p write pid to pidfile\n", + argv[0]); + return (opt == '?' || opt == 'h') ? EXIT_SUCCESS : EXIT_FAILURE; + } + } + +#if defined(ENABLE_HTTPS_PROXY) + SSL_library_init (); + ERR_load_crypto_strings(); + SSL_load_error_strings (); + OpenSSL_add_all_algorithms (); +#endif + // Wait for network ready before further initializations so that + // parser can resolve domain names. + if (wait) + wait_for_network(); + + FILE *f = fopen(confname, "r"); + if (!f) { + perror("Unable to open config file"); + return EXIT_FAILURE; + } + + parser_context* parser = parser_start(f); + if (!parser) { + perror("Not enough memory for parser"); + return EXIT_FAILURE; + } + + FOREACH(ss, subsystems) + if ((*ss)->conf_section) + parser_add_section(parser, (*ss)->conf_section); + error = parser_run(parser); + parser_stop(parser); + fclose(f); + + if (error) + return EXIT_FAILURE; + + if (conftest) + return EXIT_SUCCESS; + + if (setup_signals()) + return EXIT_FAILURE; + + memset(terminators, 0, sizeof(terminators)); + + FOREACH(ss, subsystems) { + if ((*ss)->init) { + error = (*ss)->init(); + if (error) + goto shutdown; + } + // init global event base only after base subsystem init is done. + if (!g_event_base) { + // Initialize global event base + g_event_base = event_base_new(); + if (!g_event_base) + goto shutdown; + } + } + + if (pidfile) { + f = fopen(pidfile, "w"); + if (!f) { + perror("Unable to open pidfile for write"); + return EXIT_FAILURE; + } + fprintf(f, "%d\n", getpid()); + fclose(f); + } + + assert(SIZEOF_ARRAY(exit_signals) == SIZEOF_ARRAY(terminators)); + for (i = 0; i < SIZEOF_ARRAY(exit_signals); i++) { + terminators[i] = evsignal_new(get_event_base(), exit_signals[i], terminate, NULL); + if (!terminators[i]) { + log_errno(LOG_ERR, "evsignal_new"); + goto shutdown; + } + if (evsignal_add(terminators[i], NULL) != 0) { + log_errno(LOG_ERR, "evsignal_add"); + goto shutdown; + } + } + + dumper = evsignal_new(get_event_base(), SIGUSR1, dump_handler, NULL); + if (!dumper) { + log_errno(LOG_ERR, "evsignal_new"); + goto shutdown; + } + if (evsignal_add(dumper, NULL) != 0) { + log_errno(LOG_ERR, "evsignal_add"); + goto shutdown; + } + + log_error(LOG_NOTICE, "redsocks started with: %s", event_base_get_method(g_event_base)); + + event_base_dispatch(g_event_base); + + log_error(LOG_NOTICE, "redsocks goes down"); shutdown: - for (i = 0; i < SIZEOF_ARRAY(exit_signals); i++) { - if (signal_initialized(&terminators[i])) { - if (signal_del(&terminators[i]) != 0) - log_errno(LOG_WARNING, "signal_del"); - memset(&terminators[i], 0, sizeof(terminators[i])); - } - } + if (dumper) { + if (evsignal_del(dumper) != 0) + log_errno(LOG_WARNING, "evsignal_del"); + event_free(dumper); + } - for (--ss; ss >= subsystems; ss--) - if ((*ss)->fini) - (*ss)->fini(); + for (i = 0; i < SIZEOF_ARRAY(exit_signals); i++) { + if (terminators[i]) { + if (evsignal_del(terminators[i]) != 0) + log_errno(LOG_WARNING, "evsignal_del"); + event_free(terminators[i]); + } + } - event_base_free(NULL); + for (--ss; ss >= subsystems; ss--) + if ((*ss)->fini) + (*ss)->fini(); - return !error ? EXIT_SUCCESS : EXIT_FAILURE; + if (g_event_base) + event_base_free(g_event_base); + +#if defined(ENABLE_HTTPS_PROXY) + EVP_cleanup(); + ERR_free_strings(); +#endif + return !error ? EXIT_SUCCESS : EXIT_FAILURE; } /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ diff --git a/main.h b/main.h index ca49c702..6473cee8 100644 --- a/main.h +++ b/main.h @@ -6,6 +6,7 @@ typedef struct app_subsys_t { int (*init)(); int (*fini)(); + void (*dump)();// Allow subsystem to dump information parser_section* conf_section; } app_subsys; @@ -13,6 +14,7 @@ typedef struct app_subsys_t { #define FOREACH(ptr, array) for (ptr = array; ptr < array + SIZEOF_ARRAY(array); ptr++) #define FOREACH_REV(ptr, array) for (ptr = array + SIZEOF_ARRAY(array) - 1; ptr >= array; ptr--) +struct event_base * get_event_base(); /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ diff --git a/md5.c b/md5.c index 8e0e2a5b..af37531a 100644 --- a/md5.c +++ b/md5.c @@ -308,7 +308,7 @@ static void md5_process(md5_state_t *pms, const md5_byte_t *data /*[64]*/) pms->abcd[3] += d; } -void md5_init(md5_state_t *pms) +void md5_init_rs(md5_state_t *pms) { pms->count[0] = pms->count[1] = 0; pms->abcd[0] = 0x67452301; @@ -354,7 +354,7 @@ void md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes) memcpy(pms->buf, p, left); } -void md5_finish(md5_state_t *pms, md5_byte_t digest[16]) +void md5_finish_rs(md5_state_t *pms, md5_byte_t digest[16]) { static const md5_byte_t pad[64] = { 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, diff --git a/md5.h b/md5.h index 698c995d..fa90736c 100644 --- a/md5.h +++ b/md5.h @@ -76,13 +76,13 @@ extern "C" #endif /* Initialize the algorithm. */ -void md5_init(md5_state_t *pms); +void md5_init_rs(md5_state_t *pms); /* Append a string to the message. */ void md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes); /* Finish the message and return the digest. */ -void md5_finish(md5_state_t *pms, md5_byte_t digest[16]); +void md5_finish_rs(md5_state_t *pms, md5_byte_t digest[16]); #ifdef __cplusplus } /* end extern "C" */ diff --git a/parser.c b/parser.c index 8803a511..fc2b58bd 100644 --- a/parser.c +++ b/parser.c @@ -55,10 +55,10 @@ void parser_error(parser_context *context, const char *fmt, ...) va_start(ap, fmt); if (buff) { evbuffer_add_vprintf(buff, fmt, ap); - msg = (const char*)EVBUFFER_DATA(buff); + msg = (const char*)evbuffer_pullup(buff, -1); } else - msg = error_lowmem; + msg = ""; va_end(ap); context->error = 1; @@ -297,6 +297,22 @@ static int vp_uint16(parser_context *context, void *addr, const char *token) return 0; } +static int vp_uint32(parser_context *context, void *addr, const char *token) +{ + char *end; + unsigned long int uli = strtoul(token, &end, 0); + if (uli > 0xFFFFFFFF) { + parser_error(context, "integer out of 32bit range"); + return -1; + } + if (*end != '\0') { + parser_error(context, "integer is not parsed"); + return -1; + } + *(uint32_t*)addr = (uint32_t)uli; + return 0; +} + static int vp_in_addr(parser_context *context, void *addr, const char *token) { struct in_addr ia; @@ -397,6 +413,7 @@ static value_parser value_parser_by_type[] = [pt_bool] = vp_pbool, [pt_pchar] = vp_pchar, [pt_uint16] = vp_uint16, + [pt_uint32] = vp_uint32, [pt_in_addr] = vp_in_addr, [pt_in_addr2] = vp_in_addr2, }; @@ -499,7 +516,7 @@ int parser_run(parser_context *context) parser_error(context, "section->onenter failed"); } else { - parser_error(context, "unknown section"); + parser_error(context, "unknown section <%s>", section_token); } FREE(section_token); } diff --git a/parser.h b/parser.h index 085681c1..7b101f2e 100644 --- a/parser.h +++ b/parser.h @@ -8,6 +8,7 @@ typedef enum { pt_bool, // "bool" from stdbool.h, not "_Bool" or anything else pt_pchar, pt_uint16, + pt_uint32, pt_in_addr, pt_in_addr2, // inaddr[0] = net, inaddr[1] = netmask } parser_type; diff --git a/reddns.c b/reddns.c deleted file mode 100644 index 1ca12ab1..00000000 --- a/reddns.c +++ /dev/null @@ -1,96 +0,0 @@ -/* redsocks - transparent TCP-to-proxy redirector - * Copyright (C) 2007-2011 Leonid Evdokimov - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy - * of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - */ - -#include -#include -#include -#include "parser.h" -#include "main.h" - -typedef struct reddns_config_t reddns_config; -struct reddns_config_t { - struct sockaddr_in bindaddr; - struct sockaddr_in relayaddr; - struct in_addr fakenet; - struct in_addr fakenetmask; -}; - -int reddns_onenter(parser_section *section) -{ - reddns_config *conf = calloc(1, sizeof(reddns_config)); - section->data = conf; - section->entries[0].addr = &conf->bindaddr.sin_port; - section->entries[1].addr = &conf->fakenet; - section->entries[2].addr = &conf->fakenetmask; - section->entries[3].addr = &conf->relayaddr.sin_addr; - section->entries[4].addr = &conf->relayaddr.sin_port; - fprintf(stderr, "%s\n", __FUNCTION__); - return 0; -} - -int reddns_onexit(parser_section *section) -{ - reddns_config *conf = section->data; - fprintf(stderr, - "%s {\n" - "local_port = %u;\n" - "fakeip_net = %s/%s;\n" - "ip = %s;\n" - "port = %u;\n" - "}\n", - __FUNCTION__, - conf->bindaddr.sin_port, - strdup(inet_ntoa(conf->fakenet)), - strdup(inet_ntoa(conf->fakenetmask)), - strdup(inet_ntoa(conf->relayaddr.sin_addr)), - conf->relayaddr.sin_port - ); - return 0; -} - -parser_entry reddns_entries[] = { - { .key = "local_port", .type = pt_uint16 }, - { .key = "fakeip_net", .type = pt_in_addr2 }, - { .key = "fakeip_netmask", .type = pt_in_addr }, - { .key = "ip", .type = pt_in_addr }, - { .key = "port", .type = pt_uint16 }, - { } -}; - -parser_section reddns_conf_section = { - .name = "reddns", - .entries = reddns_entries, - .onenter = reddns_onenter, - .onexit = reddns_onexit -}; - -int reddns_init() { -#error It's non-working stub at the moment. - return 0; -} - -int reddns_fini() { - return 0; -} - -app_subsys reddns_subsys = { - .init = reddns_init, - .fini = reddns_fini, - .conf_section = &reddns_conf_section, -}; - -/* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ -/* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ diff --git a/redsocks.c b/redsocks.c index b65fb1a9..2a089d37 100644 --- a/redsocks.c +++ b/redsocks.c @@ -1,4 +1,10 @@ -/* redsocks - transparent TCP-to-proxy redirector +/* redsocks2 - transparent TCP/UDP-to-proxy redirector + * Copyright (C) 2013-2017 Zhuofei Wang + * + * This code is based on redsocks project developed by Leonid Evdokimov. + * Licensed under the Apache License, Version 2.0 (the "License"). + * + * * Copyright (C) 2007-2011 Leonid Evdokimov * * Licensed under the Apache License, Version 2.0 (the "License"); you may not @@ -21,11 +27,12 @@ #include #include #include -#include #include #include #include -#include +#include +#include +#include #include "list.h" #include "parser.h" #include "log.h" @@ -33,922 +40,1125 @@ #include "base.h" #include "redsocks.h" #include "utils.h" -#include "libevent-compat.h" -#define REDSOCKS_RELAY_HALFBUFF 4096 - -enum pump_state_t { - pump_active = -1, - pump_MAX = 0, -}; - -static void redsocks_shutdown(redsocks_client *client, struct bufferevent *buffev, int how); - +#define REDSOCKS_RELAY_HALFBUFF 1024*16 +#define REDSOCKS_AUDIT_INTERVAL 60*2 +static void redsocks_relay_relayreadcb(struct bufferevent *from, void *_client); +static void redsocks_relay_relaywritecb(struct bufferevent *from, void *_client); +void redsocks_event_error(struct bufferevent *buffev, short what, void *_arg); +extern relay_subsys direct_connect_subsys; extern relay_subsys http_connect_subsys; +#if defined(ENABLE_HTTPS_PROXY) +extern relay_subsys https_connect_subsys; +#endif extern relay_subsys http_relay_subsys; extern relay_subsys socks4_subsys; extern relay_subsys socks5_subsys; +#if !defined(DISABLE_SHADOWSOCKS) +extern relay_subsys shadowsocks_subsys; +#endif static relay_subsys *relay_subsystems[] = { - &http_connect_subsys, - &http_relay_subsys, - &socks4_subsys, - &socks5_subsys, + &direct_connect_subsys, + &http_connect_subsys, + &http_relay_subsys, + &socks4_subsys, + &socks5_subsys, +#if !defined(DISABLE_SHADOWSOCKS) + &shadowsocks_subsys, +#endif +#if defined(ENABLE_HTTPS_PROXY) + &https_connect_subsys, +#endif }; +extern relay_subsys autoproxy_subsys; static list_head instances = LIST_HEAD_INIT(instances); static parser_entry redsocks_entries[] = { - { .key = "local_ip", .type = pt_in_addr }, - { .key = "local_port", .type = pt_uint16 }, - { .key = "ip", .type = pt_in_addr }, - { .key = "port", .type = pt_uint16 }, - { .key = "type", .type = pt_pchar }, - { .key = "login", .type = pt_pchar }, - { .key = "password", .type = pt_pchar }, - { .key = "listenq", .type = pt_uint16 }, - { .key = "min_accept_backoff", .type = pt_uint16 }, - { .key = "max_accept_backoff", .type = pt_uint16 }, - { } + { .key = "bind", .type = pt_pchar }, + { .key = "interface", .type = pt_pchar }, + { .key = "relay", .type = pt_pchar }, + { .key = "type", .type = pt_pchar }, + { .key = "login", .type = pt_pchar }, + { .key = "password", .type = pt_pchar }, + { .key = "listenq", .type = pt_uint16 }, + { .key = "min_accept_backoff", .type = pt_uint16 }, + { .key = "max_accept_backoff", .type = pt_uint16 }, + { .key = "autoproxy", .type = pt_uint16 }, + { .key = "timeout", .type = pt_uint16 }, + { } }; /* There is no way to get `EVLIST_INSERTED` event flag outside of libevent, so * here are tracking functions. */ static void tracked_event_set( - struct tracked_event *tev, evutil_socket_t fd, short events, - void (*callback)(evutil_socket_t, short, void *), void *arg) + struct tracked_event *tev, evutil_socket_t fd, short events, + void (*callback)(evutil_socket_t, short, void *), void *arg) { - event_set(&tev->ev, fd, events, callback, arg); - timerclear(&tev->inserted); + tev->ev = event_new(get_event_base(), fd, events, callback, arg); + timerclear(&tev->inserted); } static int tracked_event_add(struct tracked_event *tev, const struct timeval *tv) { - int ret = event_add(&tev->ev, tv); - if (ret == 0) - gettimeofday(&tev->inserted, NULL); - return ret; + int ret = event_add(tev->ev, tv); + if (ret == 0) + gettimeofday(&tev->inserted, NULL); + return ret; } static int tracked_event_del(struct tracked_event *tev) { - int ret = event_del(&tev->ev); - if (ret == 0) - timerclear(&tev->inserted); - return ret; + int ret = -1; + if (tev->ev) { + ret = event_del(tev->ev); + if (ret == 0) { + timerclear(&tev->inserted); + } + } + return ret; +} + +static void tracked_event_free(struct tracked_event *tev) +{ + if (tev->ev) { + if (timerisset(&tev->inserted)) { + event_del(tev->ev); + timerclear(&tev->inserted); + } + event_free(tev->ev); + tev->ev = NULL; + } } static int redsocks_onenter(parser_section *section) { - // FIXME: find proper way to calulate instance_payload_len - int instance_payload_len = 0; - relay_subsys **ss; - FOREACH(ss, relay_subsystems) - if (instance_payload_len < (*ss)->instance_payload_len) - instance_payload_len = (*ss)->instance_payload_len; - - redsocks_instance *instance = calloc(1, sizeof(*instance) + instance_payload_len); - if (!instance) { - parser_error(section->context, "Not enough memory"); - return -1; - } - - INIT_LIST_HEAD(&instance->list); - INIT_LIST_HEAD(&instance->clients); - instance->config.bindaddr.sin_family = AF_INET; - instance->config.bindaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - instance->config.relayaddr.sin_family = AF_INET; - instance->config.relayaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - /* Default value can be checked in run-time, but I doubt anyone needs that. - * Linux: sysctl net.core.somaxconn - * FreeBSD: sysctl kern.ipc.somaxconn */ - instance->config.listenq = SOMAXCONN; - instance->config.min_backoff_ms = 100; - instance->config.max_backoff_ms = 60000; - - for (parser_entry *entry = §ion->entries[0]; entry->key; entry++) - entry->addr = - (strcmp(entry->key, "local_ip") == 0) ? (void*)&instance->config.bindaddr.sin_addr : - (strcmp(entry->key, "local_port") == 0) ? (void*)&instance->config.bindaddr.sin_port : - (strcmp(entry->key, "ip") == 0) ? (void*)&instance->config.relayaddr.sin_addr : - (strcmp(entry->key, "port") == 0) ? (void*)&instance->config.relayaddr.sin_port : - (strcmp(entry->key, "type") == 0) ? (void*)&instance->config.type : - (strcmp(entry->key, "login") == 0) ? (void*)&instance->config.login : - (strcmp(entry->key, "password") == 0) ? (void*)&instance->config.password : - (strcmp(entry->key, "listenq") == 0) ? (void*)&instance->config.listenq : - (strcmp(entry->key, "min_accept_backoff") == 0) ? (void*)&instance->config.min_backoff_ms : - (strcmp(entry->key, "max_accept_backoff") == 0) ? (void*)&instance->config.max_backoff_ms : - NULL; - section->data = instance; - return 0; + // FIXME: find proper way to calulate instance_payload_len + int instance_payload_len = 0; + relay_subsys **ss; + FOREACH(ss, relay_subsystems) + if (instance_payload_len < (*ss)->instance_payload_len) + instance_payload_len = (*ss)->instance_payload_len; + + redsocks_instance *instance = calloc(1, sizeof(*instance) + instance_payload_len); + if (!instance) { + parser_error(section->context, "Not enough memory"); + return -1; + } + + INIT_LIST_HEAD(&instance->list); + INIT_LIST_HEAD(&instance->clients); + struct sockaddr_in * addr = (struct sockaddr_in *)&instance->config.bindaddr; + addr->sin_family = AF_INET; + addr->sin_addr.s_addr = htonl(INADDR_LOOPBACK); + /* Default value can be checked in run-time, but I doubt anyone needs that. + * Linux: sysctl net.core.somaxconn + * FreeBSD: sysctl kern.ipc.somaxconn */ + instance->config.listenq = SOMAXCONN; + instance->config.min_backoff_ms = 100; + instance->config.max_backoff_ms = 60000; + instance->config.autoproxy = 0; + instance->config.timeout = 0; + + for (parser_entry *entry = §ion->entries[0]; entry->key; entry++) + entry->addr = + (strcmp(entry->key, "interface") == 0) ? (void*)&instance->config.interface : + (strcmp(entry->key, "bind") == 0) ? (void*)&instance->config.bind : + (strcmp(entry->key, "relay") == 0) ? (void*)&instance->config.relay : + (strcmp(entry->key, "type") == 0) ? (void*)&instance->config.type : + (strcmp(entry->key, "login") == 0) ? (void*)&instance->config.login : + (strcmp(entry->key, "password") == 0) ? (void*)&instance->config.password : + (strcmp(entry->key, "listenq") == 0) ? (void*)&instance->config.listenq : + (strcmp(entry->key, "min_accept_backoff") == 0) ? (void*)&instance->config.min_backoff_ms : + (strcmp(entry->key, "max_accept_backoff") == 0) ? (void*)&instance->config.max_backoff_ms : + (strcmp(entry->key, "autoproxy") == 0) ? (void*)&instance->config.autoproxy : + (strcmp(entry->key, "timeout") == 0) ? (void*)&instance->config.timeout: + NULL; + section->data = instance; + return 0; } static int redsocks_onexit(parser_section *section) { - /* FIXME: Rewrite in bullet-proof style. There are memory leaks if config - * file is not correct, so correct on-the-fly config reloading is - * currently impossible. - */ - const char *err = NULL; - redsocks_instance *instance = section->data; - - section->data = NULL; - for (parser_entry *entry = §ion->entries[0]; entry->key; entry++) - entry->addr = NULL; - - instance->config.bindaddr.sin_port = htons(instance->config.bindaddr.sin_port); - instance->config.relayaddr.sin_port = htons(instance->config.relayaddr.sin_port); - - if (instance->config.type) { - relay_subsys **ss; - FOREACH(ss, relay_subsystems) { - if (!strcmp((*ss)->name, instance->config.type)) { - instance->relay_ss = *ss; - list_add(&instance->list, &instances); - break; - } - } - if (!instance->relay_ss) - err = "invalid `type` for redsocks"; - } - else { - err = "no `type` for redsocks"; - } - - if (!err && !instance->config.min_backoff_ms) { - err = "`min_accept_backoff` must be positive, 0 ms is too low"; - } - - if (!err && !instance->config.max_backoff_ms) { - err = "`max_accept_backoff` must be positive, 0 ms is too low"; - } - - if (!err && !(instance->config.min_backoff_ms < instance->config.max_backoff_ms)) { - err = "`min_accept_backoff` must be less than `max_accept_backoff`"; - } - - if (err) - parser_error(section->context, "%s", err); - - return err ? -1 : 0; + /* FIXME: Rewrite in bullet-proof style. There are memory leaks if config + * file is not correct, so correct on-the-fly config reloading is + * currently impossible. + */ + const char *err = NULL; + redsocks_instance *instance = section->data; + + section->data = NULL; + for (parser_entry *entry = §ion->entries[0]; entry->key; entry++) + entry->addr = NULL; + + // Parse and update bind address and relay address + if (instance->config.bind) { + struct sockaddr * addr = (struct sockaddr *)&instance->config.bindaddr; + int addr_size = sizeof(instance->config.bindaddr); + if (evutil_parse_sockaddr_port(instance->config.bind, addr, &addr_size)) + err = "invalid bind address"; + } + if (!err && instance->config.relay) { + struct sockaddr * addr = (struct sockaddr *)&instance->config.relayaddr; + int addr_size = sizeof(instance->config.relayaddr); + if (evutil_parse_sockaddr_port(instance->config.relay, addr, &addr_size)) { + char * pos = strchr(instance->config.relay, ':'); + char * host = NULL; + if (pos != NULL) + host = strndup(instance->config.relay, pos - instance->config.relay); + else + host = instance->config.relay; + int result = resolve_hostname(host, AF_INET, addr); + if (result != 0) { + result = resolve_hostname(host, AF_INET6, addr); + } + if (result != 0) { + err = "invalid relay address"; + } + if (!err && pos != NULL) { + if (addr->sa_family == AF_INET) + ((struct sockaddr_in*)addr)->sin_port = htons(atoi(pos+1)); + else + ((struct sockaddr_in6*)addr)->sin6_port = htons(atoi(pos+1)); + } + if (host != instance->config.relay) + free(host); + } + } + + if (!err && instance->config.type) { + relay_subsys **ss; + FOREACH(ss, relay_subsystems) { + if (!strcmp((*ss)->name, instance->config.type)) { + instance->relay_ss = *ss; + list_add(&instance->list, &instances); + break; + } + } + if (!instance->relay_ss) + err = "invalid `type` for redsocks"; + } + else if (!err) { + err = "no `type` for redsocks"; + } + + if (!err && !instance->config.min_backoff_ms) { + err = "`min_accept_backoff` must be positive, 0 ms is too low"; + } + + if (!err && !instance->config.max_backoff_ms) { + err = "`max_accept_backoff` must be positive, 0 ms is too low"; + } + + if (!err && !(instance->config.min_backoff_ms < instance->config.max_backoff_ms)) { + err = "`min_accept_backoff` must be less than `max_accept_backoff`"; + } + + if (err) + parser_error(section->context, "%s", err); + + if (instance->config.timeout == 0) + instance->config.timeout = DEFAULT_CONNECT_TIMEOUT; + return err ? -1 : 0; } static parser_section redsocks_conf_section = { - .name = "redsocks", - .entries = redsocks_entries, - .onenter = redsocks_onenter, - .onexit = redsocks_onexit + .name = "redsocks", + .entries = redsocks_entries, + .onenter = redsocks_onenter, + .onexit = redsocks_onexit }; void redsocks_log_write_plain( - const char *file, int line, const char *func, int do_errno, - const struct sockaddr_in *clientaddr, const struct sockaddr_in *destaddr, - int priority, const char *orig_fmt, ... + const char *file, int line, const char *func, int do_errno, + const struct sockaddr_storage *clientaddr, + const struct sockaddr_storage *destaddr, + int priority, const char *orig_fmt, ... ) { - int saved_errno = errno; - struct evbuffer *fmt = evbuffer_new(); - va_list ap; - char clientaddr_str[RED_INET_ADDRSTRLEN], destaddr_str[RED_INET_ADDRSTRLEN]; - - if (!fmt) { - log_errno(LOG_ERR, "evbuffer_new()"); - // no return, as I have to call va_start/va_end - } - - if (fmt) { - evbuffer_add_printf(fmt, "[%s->%s]: %s", - red_inet_ntop(clientaddr, clientaddr_str, sizeof(clientaddr_str)), - red_inet_ntop(destaddr, destaddr_str, sizeof(destaddr_str)), - orig_fmt); - } - - va_start(ap, orig_fmt); - if (fmt) { - errno = saved_errno; - _log_vwrite(file, line, func, do_errno, priority, (const char*)EVBUFFER_DATA(fmt), ap); - evbuffer_free(fmt); - } - va_end(ap); + int saved_errno = errno; + va_list ap; + char clientaddr_str[RED_INET_ADDRSTRLEN], destaddr_str[RED_INET_ADDRSTRLEN]; + char fmt[MAX_LOG_LENGTH+1]; + + if (!log_level_enabled(priority)) + return; + + snprintf(fmt, sizeof(fmt), "[%s->%s]: %s", + red_inet_ntop(clientaddr, clientaddr_str, sizeof(clientaddr_str)), + red_inet_ntop(destaddr, destaddr_str, sizeof(destaddr_str)), + orig_fmt); + + va_start(ap, orig_fmt); + errno = saved_errno; + _log_vwrite(file, line, func, do_errno, priority, &fmt[0], ap); + va_end(ap); } void redsocks_touch_client(redsocks_client *client) { - redsocks_time(&client->last_event); + redsocks_time(&client->last_event); } static inline const char* bufname(redsocks_client *client, struct bufferevent *buf) { - assert(buf == client->client || buf == client->relay); - return buf == client->client ? "client" : "relay"; + assert(buf == client->client || buf == client->relay); + return buf == client->client ? "client" : "relay"; } static void redsocks_relay_readcb(redsocks_client *client, struct bufferevent *from, struct bufferevent *to) { - if (EVBUFFER_LENGTH(to->output) < to->wm_write.high) { - if (bufferevent_write_buffer(to, from->input) == -1) - redsocks_log_errno(client, LOG_ERR, "bufferevent_write_buffer"); - } - else { - if (bufferevent_get_enabled(from) & EV_READ) { - redsocks_log_error(client, LOG_DEBUG, "backpressure: bufferevent_disable(%s, EV_READ)", bufname(client, from)); - if (bufferevent_disable(from, EV_READ) == -1) - redsocks_log_errno(client, LOG_ERR, "bufferevent_disable"); - } - } + redsocks_log_error(client, LOG_DEBUG, "RCB %s, in: %zu", from == client->client?"client":"relay", + evbuffer_get_length(bufferevent_get_input(from))); + + if (evbuffer_get_length(bufferevent_get_output(to)) < get_write_hwm(to)) { + if (bufferevent_write_buffer(to, bufferevent_get_input(from)) == -1) + redsocks_log_errno(client, LOG_ERR, "bufferevent_write_buffer"); + if (bufferevent_enable(from, EV_READ) == -1) + redsocks_log_errno(client, LOG_ERR, "bufferevent_enable"); + } + else { + if (bufferevent_disable(from, EV_READ) == -1) + redsocks_log_errno(client, LOG_ERR, "bufferevent_disable"); + } } -static void redsocks_relay_writecb(redsocks_client *client, struct bufferevent *from, struct bufferevent *to) +int process_shutdown_on_write_(redsocks_client *client, struct bufferevent *from, struct bufferevent *to) { - assert(from == client->client || from == client->relay); - char from_eof = (from == client->client ? client->client_evshut : client->relay_evshut) & EV_READ; + assert(from == client->client || from == client->relay); + unsigned short from_evshut = from == client->client ? client->client_evshut : client->relay_evshut; + unsigned short to_evshut = to == client->client ? client->client_evshut : client->relay_evshut; + + redsocks_log_error(client, LOG_DEBUG, "WCB %s, fs: %u, ts: %u, fin: %zu, fout: %zu, tin: %zu", + to == client->client?"client":"relay", + from_evshut, + to_evshut, + evbuffer_get_length(bufferevent_get_input(from)), + evbuffer_get_length(bufferevent_get_output(from)), + evbuffer_get_length(bufferevent_get_input(to))); + + if ((from_evshut & EV_READ) && !(to_evshut & EV_WRITE) + && evbuffer_get_length(bufferevent_get_input(from)) == 0) { + redsocks_shutdown(client, to, SHUT_WR, 0); + return 1; + } + return 0; +} - if (EVBUFFER_LENGTH(from->input) == 0 && from_eof) { - redsocks_shutdown(client, to, SHUT_WR); - } - else if (EVBUFFER_LENGTH(to->output) < to->wm_write.high) { - if (bufferevent_write_buffer(to, from->input) == -1) - redsocks_log_errno(client, LOG_ERR, "bufferevent_write_buffer"); - if (!from_eof && !(bufferevent_get_enabled(from) & EV_READ)) { - redsocks_log_error(client, LOG_DEBUG, "backpressure: bufferevent_enable(%s, EV_READ)", bufname(client, from)); - if (bufferevent_enable(from, EV_READ) == -1) - redsocks_log_errno(client, LOG_ERR, "bufferevent_enable"); - } - } +static void redsocks_relay_writecb(redsocks_client *client, struct bufferevent *from, struct bufferevent *to) +{ + assert(from == client->client || from == client->relay); + unsigned short from_evshut = from == client->client ? client->client_evshut : client->relay_evshut; + + if (process_shutdown_on_write_(client, from, to)) + return; + if (evbuffer_get_length(bufferevent_get_output(to)) < get_write_hwm(to)) { + if (bufferevent_write_buffer(to, bufferevent_get_input(from)) == -1) + redsocks_log_errno(client, LOG_ERR, "bufferevent_write_buffer"); + if (!(from_evshut & EV_READ) && bufferevent_enable(from, EV_READ) == -1) + redsocks_log_errno(client, LOG_ERR, "bufferevent_enable"); + } } static void redsocks_relay_relayreadcb(struct bufferevent *from, void *_client) { - redsocks_client *client = _client; - redsocks_touch_client(client); - redsocks_relay_readcb(client, client->relay, client->client); + redsocks_client *client = _client; + redsocks_touch_client(client); + redsocks_relay_readcb(client, client->relay, client->client); } static void redsocks_relay_relaywritecb(struct bufferevent *to, void *_client) { - redsocks_client *client = _client; - redsocks_touch_client(client); - redsocks_relay_writecb(client, client->client, client->relay); + redsocks_client *client = _client; + redsocks_touch_client(client); + redsocks_relay_writecb(client, client->client, client->relay); } static void redsocks_relay_clientreadcb(struct bufferevent *from, void *_client) { - redsocks_client *client = _client; - redsocks_touch_client(client); - redsocks_relay_readcb(client, client->client, client->relay); + redsocks_client *client = _client; + redsocks_touch_client(client); + redsocks_relay_readcb(client, client->client, client->relay); } static void redsocks_relay_clientwritecb(struct bufferevent *to, void *_client) { - redsocks_client *client = _client; - redsocks_touch_client(client); - redsocks_relay_writecb(client, client->relay, client->client); + redsocks_client *client = _client; + redsocks_touch_client(client); + redsocks_relay_writecb(client, client->relay, client->client); } -void redsocks_start_relay(redsocks_client *client) +int redsocks_start_relay(redsocks_client *client) { - int error; - - if (client->instance->relay_ss->fini) - client->instance->relay_ss->fini(client); - - client->state = pump_active; - - client->relay->wm_read.low = 0; - client->relay->wm_write.low = 0; - client->client->wm_read.low = 0; - client->client->wm_write.low = 0; - client->relay->wm_read.high = REDSOCKS_RELAY_HALFBUFF; - client->relay->wm_write.high = REDSOCKS_RELAY_HALFBUFF; - client->client->wm_read.high = REDSOCKS_RELAY_HALFBUFF; - client->client->wm_write.high = REDSOCKS_RELAY_HALFBUFF; - - client->client->readcb = redsocks_relay_clientreadcb; - client->client->writecb = redsocks_relay_clientwritecb; - client->relay->readcb = redsocks_relay_relayreadcb; - client->relay->writecb = redsocks_relay_relaywritecb; - - error = bufferevent_enable(client->client, (EV_READ|EV_WRITE) & ~(client->client_evshut)); - if (!error) - error = bufferevent_enable(client->relay, (EV_READ|EV_WRITE) & ~(client->relay_evshut)); - - if (!error) { - redsocks_log_error(client, LOG_DEBUG, "data relaying started"); - } - else { - redsocks_log_errno(client, LOG_ERR, "bufferevent_enable"); - redsocks_drop_client(client); - } + int error; + bufferevent_event_cb event_cb; + + bufferevent_setwatermark(client->client, EV_READ|EV_WRITE, 0, REDSOCKS_RELAY_HALFBUFF); + bufferevent_setwatermark(client->relay, EV_READ|EV_WRITE, 0, REDSOCKS_RELAY_HALFBUFF); +#if LIBEVENT_VERSION_NUMBER >= 0x02010100 + bufferevent_getcb(client->client, NULL, NULL, &event_cb, NULL); +#else + event_cb = client->client->errorcb; +#endif + bufferevent_setcb(client->client, redsocks_relay_clientreadcb, + redsocks_relay_clientwritecb, + event_cb, + client); +#if LIBEVENT_VERSION_NUMBER >= 0x02010100 + bufferevent_getcb(client->relay, NULL, NULL, &event_cb, NULL); +#else + event_cb = client->relay->errorcb; +#endif + bufferevent_setcb(client->relay, redsocks_relay_relayreadcb, + redsocks_relay_relaywritecb, + event_cb, + client); + + error = bufferevent_enable(client->client, + client->client_evshut == EV_READ ? EV_WRITE : + client->client_evshut == EV_WRITE ? EV_READ : + client->client_evshut == (EV_READ|EV_WRITE) ? 0 : EV_READ | EV_WRITE); + if (!error) + error = bufferevent_enable(client->relay, EV_READ | EV_WRITE); + + if (!error) { + redsocks_log_error(client, LOG_DEBUG, "data relaying started"); + } + else { + redsocks_log_errno(client, LOG_ERR, "bufferevent_enable"); + redsocks_drop_client(client); + } + return error; } void redsocks_drop_client(redsocks_client *client) { - redsocks_log_error(client, LOG_INFO, "dropping client"); - - if (client->instance->relay_ss->fini) - client->instance->relay_ss->fini(client); - - if (client->client) { - redsocks_close(EVENT_FD(&client->client->ev_write)); - bufferevent_free(client->client); - } - - if (client->relay) { - redsocks_close(EVENT_FD(&client->relay->ev_write)); - bufferevent_free(client->relay); - } - - list_del(&client->list); - free(client); + int fd; + redsocks_log_error(client, LOG_DEBUG, "dropping client @ state: %d", client->state); + + if (client->instance->config.autoproxy && autoproxy_subsys.fini) + autoproxy_subsys.fini(client); + + if (client->instance->relay_ss->fini) + client->instance->relay_ss->fini(client); + + if (client->client) { + fd = bufferevent_getfd(client->client); + bufferevent_disable(client->client, EV_READ|EV_WRITE); + bufferevent_free(client->client); + redsocks_close(fd); + } + + if (client->relay) { + fd = bufferevent_getfd(client->relay); + bufferevent_disable(client->relay, EV_READ|EV_WRITE); + bufferevent_free(client->relay); + redsocks_close(fd); + } + + list_del(&client->list); + free(client); } -static void redsocks_shutdown(redsocks_client *client, struct bufferevent *buffev, int how) +void redsocks_shutdown(redsocks_client *client, struct bufferevent *buffev, int how, int pseudo) { - short evhow = 0; - const char *strev, *strhow = NULL, *strevhow = NULL; - unsigned short *pevshut; - - assert(how == SHUT_RD || how == SHUT_WR || how == SHUT_RDWR); - assert(buffev == client->client || buffev == client->relay); - assert(EVENT_FD(&buffev->ev_read) == EVENT_FD(&buffev->ev_write)); - - if (how == SHUT_RD) { - strhow = "SHUT_RD"; - evhow = EV_READ; - strevhow = "EV_READ"; - } - else if (how == SHUT_WR) { - strhow = "SHUT_WR"; - evhow = EV_WRITE; - strevhow = "EV_WRITE"; - } - else if (how == SHUT_RDWR) { - strhow = "SHUT_RDWR"; - evhow = EV_READ|EV_WRITE; - strevhow = "EV_READ|EV_WRITE"; - } - - assert(strhow && strevhow); - - strev = bufname(client, buffev); - pevshut = buffev == client->client ? &client->client_evshut : &client->relay_evshut; - - // if EV_WRITE is already shut and we're going to shutdown read then - // we're either going to abort data flow (bad behaviour) or confirm EOF - // and in this case socket is already SHUT_RD'ed - if ( !(how == SHUT_RD && (*pevshut & EV_WRITE)) ) - if (shutdown(EVENT_FD(&buffev->ev_read), how) != 0) - redsocks_log_errno(client, LOG_ERR, "shutdown(%s, %s)", strev, strhow); - - redsocks_log_error(client, LOG_DEBUG, "shutdown: bufferevent_disable(%s, %s)", strev, strevhow); - if (bufferevent_disable(buffev, evhow) != 0) - redsocks_log_errno(client, LOG_ERR, "bufferevent_disable(%s, %s)", strev, strevhow); - - *pevshut |= evhow; - - if (client->relay_evshut == (EV_READ|EV_WRITE) && client->client_evshut == (EV_READ|EV_WRITE)) { - redsocks_log_error(client, LOG_DEBUG, "both client and server disconnected"); - redsocks_drop_client(client); - } + short evhow = 0; + char *strev, *strhow = NULL, *strevhow = NULL; + unsigned short *pevshut; + + assert(how == SHUT_RD || how == SHUT_WR || how == SHUT_RDWR); + assert(buffev == client->client || buffev == client->relay); + + if (how == SHUT_RD) { + strhow = "SHUT_RD"; + evhow = EV_READ; + strevhow = "EV_READ"; + } + else if (how == SHUT_WR) { + strhow = "SHUT_WR"; + evhow = EV_WRITE; + strevhow = "EV_WRITE"; + } + else if (how == SHUT_RDWR) { + strhow = "SHUT_RDWR"; + evhow = EV_READ|EV_WRITE; + strevhow = "EV_READ|EV_WRITE"; + } + + assert(strhow && strevhow); + + strev = buffev == client->client ? "client" : "relay"; + pevshut = buffev == client->client ? &client->client_evshut : &client->relay_evshut; + + if (bufferevent_disable(buffev, evhow) != 0) + redsocks_log_errno(client, LOG_ERR, "bufferevent_disable(%s, %s)", strev, strevhow); + + // if EV_WRITE is already shut and we're going to shutdown read then + // we're either going to abort data flow (bad behaviour) or confirm EOF + // and in this case socket is already SHUT_RD'ed + if (!pseudo) + if ( !(how == SHUT_RD && (*pevshut & EV_WRITE)) ) + if (shutdown(bufferevent_getfd(buffev), how) != 0) { + redsocks_log_errno(client, LOG_ERR, "shutdown(%s, %s)", strev, strhow); + // In case of 'Transport endpoint is not connected', shutdown as SHUT_RDWR. + if (errno == ENOTCONN) + evhow = EV_READ|EV_WRITE; + } + + *pevshut |= evhow; + + if (client->relay_evshut == (EV_READ|EV_WRITE) && client->client_evshut == (EV_READ|EV_WRITE)) { + redsocks_log_error(client, LOG_DEBUG, "both client and server disconnected"); + redsocks_drop_client(client); + } } // I assume that -1 is invalid errno value static int redsocks_socket_geterrno(redsocks_client *client, struct bufferevent *buffev) { - int pseudo_errno = red_socket_geterrno(buffev); - if (pseudo_errno == -1) { - redsocks_log_errno(client, LOG_ERR, "red_socket_geterrno"); - return -1; - } - return pseudo_errno; + int pseudo_errno = red_socket_geterrno(buffev); + if (pseudo_errno == -1) { + redsocks_log_errno(client, LOG_ERR, "red_socket_geterrno"); + return -1; + } + return pseudo_errno; } -static void redsocks_event_error(struct bufferevent *buffev, short what, void *_arg) +void redsocks_event_error(struct bufferevent *buffev, short what, void *_arg) { - redsocks_client *client = _arg; - assert(buffev == client->relay || buffev == client->client); - - redsocks_touch_client(client); - - if (what == (EVBUFFER_READ|EVBUFFER_EOF)) { - struct bufferevent *antiev; - if (buffev == client->relay) - antiev = client->client; - else - antiev = client->relay; - - redsocks_shutdown(client, buffev, SHUT_RD); - - // If the client has already sent EOF and the pump is not active - // (relay is activating), the code should not shutdown write-pipe. - if (client->state == pump_active && antiev != NULL && EVBUFFER_LENGTH(antiev->output) == 0) - redsocks_shutdown(client, antiev, SHUT_WR); - } - else { - errno = redsocks_socket_geterrno(client, buffev); - redsocks_log_errno(client, LOG_NOTICE, "%s error, code " event_fmt_str, - bufname(client, buffev), - event_fmt(what)); - redsocks_drop_client(client); - } + redsocks_client *client = _arg; + assert(buffev == client->relay || buffev == client->client); + + redsocks_touch_client(client); + + if (!(what & BEV_EVENT_ERROR)) + errno = redsocks_socket_geterrno(client, buffev); + redsocks_log_errno(client, LOG_DEBUG, "%s, what: " event_fmt_str, + buffev == client->client?"client":"relay", + event_fmt(what)); + + if (what == (BEV_EVENT_READING|BEV_EVENT_EOF)) { + redsocks_shutdown(client, buffev, SHUT_RD, 1); + // Ensure the other party could send remaining data and SHUT_WR also + if (buffev == client->client) + { + if (!(client->relay_evshut & EV_WRITE) && client->relay_connected) + bufferevent_enable(client->relay, EV_WRITE); + } + else + { + if (!(client->client_evshut & EV_WRITE)) + bufferevent_enable(client->client, EV_WRITE); + } + } + else { + redsocks_drop_client(client); + } } int sizes_equal(size_t a, size_t b) { - return a == b; + return a == b; } int sizes_greater_equal(size_t a, size_t b) { - return a >= b; + return a >= b; } int redsocks_read_expected(redsocks_client *client, struct evbuffer *input, void *data, size_comparator comparator, size_t expected) { - size_t len = EVBUFFER_LENGTH(input); - if (comparator(len, expected)) { - int read = evbuffer_remove(input, data, expected); - UNUSED(read); - assert(read == expected); - return 0; - } - else { - redsocks_log_error(client, LOG_NOTICE, "Can't get expected amount of data"); - redsocks_drop_client(client); - return -1; - } + size_t len = evbuffer_get_length(input); + if (comparator(len, expected)) { + int read = evbuffer_remove(input, data, expected); + UNUSED(read); + assert(read == expected); + return 0; + } + else { + redsocks_log_error(client, LOG_NOTICE, "Can't get expected amount of data"); + redsocks_drop_client(client); + return -1; + } } struct evbuffer *mkevbuffer(void *data, size_t len) { - struct evbuffer *buff = NULL, *retval = NULL; + struct evbuffer *buff = NULL, *retval = NULL; - buff = evbuffer_new(); - if (!buff) { - log_errno(LOG_ERR, "evbuffer_new"); - goto fail; - } + buff = evbuffer_new(); + if (!buff) { + log_errno(LOG_ERR, "evbuffer_new"); + goto fail; + } - if (evbuffer_add(buff, data, len) < 0) { - log_errno(LOG_ERR, "evbuffer_add"); - goto fail; - } + if (evbuffer_add(buff, data, len) < 0) { + log_errno(LOG_ERR, "evbuffer_add"); + goto fail; + } - retval = buff; - buff = NULL; + retval = buff; + buff = NULL; fail: - if (buff) - evbuffer_free(buff); - return retval; + if (buff) + evbuffer_free(buff); + return retval; } int redsocks_write_helper_ex( - struct bufferevent *buffev, redsocks_client *client, - redsocks_message_maker mkmessage, int state, size_t wm_low, size_t wm_high) + struct bufferevent *buffev, redsocks_client *client, + redsocks_message_maker mkmessage, int state, size_t wm_low, size_t wm_high) { - assert(client); - return redsocks_write_helper_ex_plain(buffev, client, (redsocks_message_maker_plain)mkmessage, - client, state, wm_low, wm_high); + assert(client); + return redsocks_write_helper_ex_plain(buffev, client, (redsocks_message_maker_plain)mkmessage, + client, state, wm_low, wm_high); } int redsocks_write_helper_ex_plain( - struct bufferevent *buffev, redsocks_client *client, - redsocks_message_maker_plain mkmessage, void *p, int state, size_t wm_low, size_t wm_high) -{ - int len; - struct evbuffer *buff = NULL; - int drop = 1; - - if (mkmessage) { - buff = mkmessage(p); - if (!buff) - goto fail; - - assert(!client || buffev == client->relay); - len = bufferevent_write_buffer(buffev, buff); - if (len < 0) { - if (client) - redsocks_log_errno(client, LOG_ERR, "bufferevent_write_buffer"); - else - log_errno(LOG_ERR, "bufferevent_write_buffer"); - goto fail; - } - } - - if (client) - client->state = state; - buffev->wm_read.low = wm_low; - buffev->wm_read.high = wm_high; - bufferevent_enable(buffev, EV_READ); - drop = 0; + struct bufferevent *buffev, redsocks_client *client, + redsocks_message_maker_plain mkmessage, void *p, int state, size_t wm_low, size_t wm_high) +{ + int len; + struct evbuffer *buff = NULL; + int drop = 1; + + if (mkmessage) { + buff = mkmessage(p); + if (!buff) + goto fail; + + assert(!client || buffev == client->relay); + len = bufferevent_write_buffer(buffev, buff); + if (len < 0) { + if (client) + redsocks_log_errno(client, LOG_ERR, "bufferevent_write_buffer"); + else + log_errno(LOG_ERR, "bufferevent_write_buffer"); + goto fail; + } + } + + if (client) + client->state = state; + bufferevent_setwatermark(buffev, EV_READ, wm_low, wm_high); + bufferevent_enable(buffev, EV_READ); + drop = 0; fail: - if (buff) - evbuffer_free(buff); - if (drop && client) - redsocks_drop_client(client); - return drop ? -1 : 0; + if (buff) + evbuffer_free(buff); + if (drop && client) + redsocks_drop_client(client); + return drop ? -1 : 0; } int redsocks_write_helper( - struct bufferevent *buffev, redsocks_client *client, - redsocks_message_maker mkmessage, int state, size_t wm_only) + struct bufferevent *buffev, redsocks_client *client, + redsocks_message_maker mkmessage, int state, size_t wm_only) { - assert(client); - return redsocks_write_helper_ex(buffev, client, mkmessage, state, wm_only, wm_only); + assert(client); + return redsocks_write_helper_ex(buffev, client, mkmessage, state, wm_only, wm_only); } -static void redsocks_relay_connected(struct bufferevent *buffev, void *_arg) +void redsocks_relay_connected(struct bufferevent *buffev, void *_arg) { - redsocks_client *client = _arg; - - assert(buffev == client->relay); - - redsocks_touch_client(client); - - if (!red_is_socket_connected_ok(buffev)) { - redsocks_log_errno(client, LOG_NOTICE, "red_is_socket_connected_ok"); - goto fail; - } - - client->relay->readcb = client->instance->relay_ss->readcb; - client->relay->writecb = client->instance->relay_ss->writecb; - client->relay->writecb(buffev, _arg); - return; + redsocks_client *client = _arg; + assert(buffev == client->relay); + + redsocks_touch_client(client); + + if (!red_is_socket_connected_ok(buffev)) { + redsocks_log_errno(client, LOG_NOTICE, "red_is_socket_connected_ok"); + goto fail; + } + client->relay_connected = 1; + /* We do not need to detect timeouts any more. + The two peers will handle it. */ + bufferevent_set_timeouts(client->relay, NULL, NULL); + bufferevent_setcb(client->relay, client->instance->relay_ss->readcb, + client->instance->relay_ss->writecb, + redsocks_event_error, + client); +#if LIBEVENT_VERSION_NUMBER >= 0x02010100 + bufferevent_trigger(client->relay, EV_WRITE, 0); +#else + if (client->instance->relay_ss->writecb) + client->instance->relay_ss->writecb(client->relay, client); +#endif + return; fail: - redsocks_drop_client(client); + redsocks_drop_client(client); } -void redsocks_connect_relay(redsocks_client *client) +int redsocks_connect_relay(redsocks_client *client) { - client->relay = red_connect_relay(&client->instance->config.relayaddr, - redsocks_relay_connected, redsocks_event_error, client); - if (!client->relay) { - redsocks_log_errno(client, LOG_ERR, "red_connect_relay"); - redsocks_drop_client(client); - } + char * interface = client->instance->config.interface; + struct timeval tv; + tv.tv_sec = client->instance->config.timeout; + tv.tv_usec = 0; + + // Allowing binding relay socket to specified IP for outgoing connections + client->relay = red_connect_relay(interface, + &client->instance->config.relayaddr, + NULL, + redsocks_relay_connected, + redsocks_event_error, client, &tv); + if (!client->relay) { + redsocks_log_errno(client, LOG_ERR, "red_connect_relay failed!!!"); + redsocks_drop_client(client); + return -1; + } + return 0; } static void redsocks_accept_backoff(int fd, short what, void *_arg) { - redsocks_instance *self = _arg; + redsocks_instance *self = _arg; - /* Isn't it already deleted? EV_PERSIST has nothing common with timeouts in - * old libevent... On the other hand libevent does not return any error. */ - if (tracked_event_del(&self->accept_backoff) != 0) - log_errno(LOG_ERR, "event_del"); + /* Isn't it already deleted? EV_PERSIST has nothing common with timeouts in + * old libevent... On the other hand libevent does not return any error. */ + if (tracked_event_del(&self->accept_backoff) != 0) + log_errno(LOG_ERR, "event_del"); - if (tracked_event_add(&self->listener, NULL) != 0) - log_errno(LOG_ERR, "event_add"); + if (tracked_event_add(&self->listener, NULL) != 0) + log_errno(LOG_ERR, "event_add"); } void redsocks_close_internal(int fd, const char* file, int line, const char *func) { - if (close(fd) == 0) { - redsocks_instance *instance = NULL; - struct timeval now; - gettimeofday(&now, NULL); - list_for_each_entry(instance, &instances, list) { - if (timerisset(&instance->accept_backoff.inserted)) { - struct timeval min_accept_backoff = { - instance->config.min_backoff_ms / 1000, - (instance->config.min_backoff_ms % 1000) * 1000}; - struct timeval time_passed; - timersub(&now, &instance->accept_backoff.inserted, &time_passed); - if (timercmp(&min_accept_backoff, &time_passed, <)) { - redsocks_accept_backoff(-1, 0, instance); - break; - } - } - } - } - else { - const int do_errno = 1; - _log_write(file, line, func, do_errno, LOG_WARNING, "close"); - } + if (close(fd) == 0) { + redsocks_instance *instance = NULL; + struct timeval now; + gettimeofday(&now, NULL); + list_for_each_entry(instance, &instances, list) { + if (timerisset(&instance->accept_backoff.inserted)) { + struct timeval min_accept_backoff = { + instance->config.min_backoff_ms / 1000, + (instance->config.min_backoff_ms % 1000) * 1000}; + struct timeval time_passed; + timersub(&now, &instance->accept_backoff.inserted, &time_passed); + if (timercmp(&min_accept_backoff, &time_passed, <)) { + redsocks_accept_backoff(-1, 0, instance); + break; + } + } + } + } + else { + const int do_errno = 1; + _log_write(file, line, func, do_errno, LOG_WARNING, "close"); + } } static void redsocks_accept_client(int fd, short what, void *_arg) { - redsocks_instance *self = _arg; - redsocks_client *client = NULL; - struct sockaddr_in clientaddr; - struct sockaddr_in myaddr; - struct sockaddr_in destaddr; - socklen_t addrlen = sizeof(clientaddr); - int client_fd = -1; - int error; - - // working with client_fd - client_fd = accept(fd, (struct sockaddr*)&clientaddr, &addrlen); - if (client_fd == -1) { - /* Different systems use different `errno` value to signal different - * `lack of file descriptors` conditions. Here are most of them. */ - if (errno == ENFILE || errno == EMFILE || errno == ENOBUFS || errno == ENOMEM) { - self->accept_backoff_ms = (self->accept_backoff_ms << 1) + 1; - clamp_value(self->accept_backoff_ms, self->config.min_backoff_ms, self->config.max_backoff_ms); - int delay = (red_randui32() % self->accept_backoff_ms) + 1; - log_errno(LOG_WARNING, "accept: out of file descriptors, backing off for %u ms", delay); - struct timeval tvdelay = { delay / 1000, (delay % 1000) * 1000 }; - if (tracked_event_del(&self->listener) != 0) - log_errno(LOG_ERR, "event_del"); - if (tracked_event_add(&self->accept_backoff, &tvdelay) != 0) - log_errno(LOG_ERR, "event_add"); - } - else { - log_errno(LOG_WARNING, "accept"); - } - goto fail; - } - self->accept_backoff_ms = 0; - - // socket is really bound now (it could be bound to 0.0.0.0) - addrlen = sizeof(myaddr); - error = getsockname(client_fd, (struct sockaddr*)&myaddr, &addrlen); - if (error) { - log_errno(LOG_WARNING, "getsockname"); - goto fail; - } - - error = getdestaddr(client_fd, &clientaddr, &myaddr, &destaddr); - if (error) { - goto fail; - } - - if (apply_tcp_keepalive(client_fd)) - goto fail; - - // everything seems to be ok, let's allocate some memory - client = calloc(1, sizeof(redsocks_client) + self->relay_ss->payload_len); - if (!client) { - log_errno(LOG_ERR, "calloc"); - goto fail; - } - client->instance = self; - memcpy(&client->clientaddr, &clientaddr, sizeof(clientaddr)); - memcpy(&client->destaddr, &destaddr, sizeof(destaddr)); - INIT_LIST_HEAD(&client->list); - self->relay_ss->init(client); - - if (redsocks_time(&client->first_event) == ((time_t)-1)) - goto fail; - - redsocks_touch_client(client); - - client->client = bufferevent_new(client_fd, NULL, NULL, redsocks_event_error, client); - if (!client->client) { - log_errno(LOG_ERR, "bufferevent_new"); - goto fail; - } - client_fd = -1; - - list_add(&client->list, &self->clients); - - // enable reading to handle EOF from client - if (bufferevent_enable(client->client, EV_READ) != 0) { - redsocks_log_errno(client, LOG_ERR, "bufferevent_enable"); - goto fail; - } - - redsocks_log_error(client, LOG_INFO, "accepted"); - - if (self->relay_ss->connect_relay) - self->relay_ss->connect_relay(client); - else - redsocks_connect_relay(client); - - return; + redsocks_instance *self = _arg; + redsocks_client *client = NULL; + struct sockaddr_storage clientaddr; + struct sockaddr_storage myaddr; + struct sockaddr_storage destaddr; + socklen_t addrlen = sizeof(clientaddr); + int client_fd = -1; + int error; + + // working with client_fd + client_fd = accept(fd, (struct sockaddr*)&clientaddr, &addrlen); + if (client_fd == -1) { + /* Different systems use different `errno` value to signal different + * `lack of file descriptors` conditions. Here are most of them. */ + if (errno == ENFILE || errno == EMFILE || errno == ENOBUFS || errno == ENOMEM) { + self->accept_backoff_ms = (self->accept_backoff_ms << 1) + 1; + clamp_value(self->accept_backoff_ms, self->config.min_backoff_ms, self->config.max_backoff_ms); + int delay = (red_randui32() % self->accept_backoff_ms) + 1; + log_errno(LOG_WARNING, "accept: out of file descriptors, backing off for %u ms", delay); + struct timeval tvdelay = { delay / 1000, (delay % 1000) * 1000 }; + if (tracked_event_del(&self->listener) != 0) + log_errno(LOG_ERR, "event_del"); + if (tracked_event_add(&self->accept_backoff, &tvdelay) != 0) + log_errno(LOG_ERR, "event_add"); + } + else { + log_errno(LOG_WARNING, "accept"); + } + goto fail; + } + self->accept_backoff_ms = 0; + + // socket is really bound now (it could be bound to 0.0.0.0) + addrlen = sizeof(myaddr); + error = getsockname(client_fd, (struct sockaddr*)&myaddr, &addrlen); + if (error) { + log_errno(LOG_WARNING, "getsockname"); + goto fail; + } + + error = getdestaddr(client_fd, &clientaddr, &myaddr, &destaddr); + if (error) { + goto fail; + } + + error = evutil_make_socket_nonblocking(client_fd); + if (error) { + log_errno(LOG_ERR, "evutil_make_socket_nonblocking"); + goto fail; + } + + if (apply_tcp_keepalive(client_fd)) + goto fail; + + // everything seems to be ok, let's allocate some memory + if (self->config.autoproxy) + client = calloc(1, sizeof(redsocks_client) + + self->relay_ss->payload_len + autoproxy_subsys.payload_len + ); + else + client = calloc(1, sizeof(redsocks_client) + self->relay_ss->payload_len); + if (!client) { + log_errno(LOG_ERR, "calloc"); + goto fail; + } + + client->instance = self; + memcpy(&client->clientaddr, &clientaddr, sizeof(clientaddr)); + memcpy(&client->destaddr, &destaddr, sizeof(destaddr)); + INIT_LIST_HEAD(&client->list); + self->relay_ss->init(client); + if (self->config.autoproxy) + autoproxy_subsys.init(client); + + if (redsocks_time(&client->first_event) == ((time_t)-1)) + goto fail; + + redsocks_touch_client(client); + + client->client = bufferevent_socket_new(get_event_base(), client_fd, 0); + if (!client->client) { + log_errno(LOG_ERR, "bufferevent_socket_new"); + goto fail; + } + bufferevent_setcb(client->client, NULL, NULL, redsocks_event_error, client); + + client_fd = -1; + + // enable reading to handle EOF from client + if (bufferevent_enable(client->client, EV_READ) != 0) { + redsocks_log_errno(client, LOG_ERR, "bufferevent_enable"); + goto fail; + } + + list_add(&client->list, &self->clients); + + redsocks_log_error(client, LOG_DEBUG, "accepted"); + + if (self->config.autoproxy && autoproxy_subsys.connect_relay) + autoproxy_subsys.connect_relay(client); + else if (self->relay_ss->connect_relay) + self->relay_ss->connect_relay(client); + else + redsocks_connect_relay(client); + + return; fail: - if (client) { - redsocks_drop_client(client); - } - if (client_fd != -1) - redsocks_close(client_fd); + if (client) { + redsocks_drop_client(client); + } + if (client_fd != -1) + redsocks_close(client_fd); } static const char *redsocks_evshut_str(unsigned short evshut) { - return - evshut == EV_READ ? "SHUT_RD" : - evshut == EV_WRITE ? "SHUT_WR" : - evshut == (EV_READ|EV_WRITE) ? "SHUT_RDWR" : - evshut == 0 ? "" : - "???"; + return + evshut == EV_READ ? "SHUT_RD" : + evshut == EV_WRITE ? "SHUT_WR" : + evshut == (EV_READ|EV_WRITE) ? "SHUT_RDWR" : + evshut == 0 ? "" : + "???"; } static const char *redsocks_event_str(unsigned short what) { - return - what == EV_READ ? "R/-" : - what == EV_WRITE ? "-/W" : - what == (EV_READ|EV_WRITE) ? "R/W" : - what == 0 ? "-/-" : - "???"; + return + what == EV_READ ? "R/-" : + what == EV_WRITE ? "-/W" : + what == (EV_READ|EV_WRITE) ? "R/W" : + what == 0 ? "-/-" : + "???"; } -static void redsocks_debug_dump_instance(redsocks_instance *instance, time_t now) +void redsocks_dump_client(redsocks_client * client, int loglevel) { - redsocks_client *client = NULL; - char bindaddr_str[RED_INET_ADDRSTRLEN]; + time_t now = redsocks_time(NULL); + + const char *s_client_evshut = redsocks_evshut_str(client->client_evshut); + const char *s_relay_evshut = redsocks_evshut_str(client->relay_evshut); + + redsocks_log_error(client, loglevel, "client(%i): (%s)%s%s input %zu output %zu, relay(%i): (%s)%s%s input %zu output %zu, age: %li sec, idle: %li sec.", + client->client ? bufferevent_getfd(client->client) : -1, + redsocks_event_str(client->client ? bufferevent_get_enabled(client->client) : 0), + s_client_evshut[0] ? " " : "", s_client_evshut, + client->client ? evbuffer_get_length(bufferevent_get_input(client->client)) : 0, + client->client ? evbuffer_get_length(bufferevent_get_output(client->client)) : 0, + client->relay ? bufferevent_getfd(client->relay) : -1, + redsocks_event_str(client->relay ? bufferevent_get_enabled(client->relay) : 0), + s_relay_evshut[0] ? " " : "", s_relay_evshut, + client->relay ? evbuffer_get_length(bufferevent_get_input(client->relay)) : 0, + client->relay ? evbuffer_get_length(bufferevent_get_output(client->relay)) : 0, + now - client->first_event, + now - client->last_event); +} - log_error(LOG_NOTICE, "Dumping client list for %s at %s:", instance->config.type, red_inet_ntop(&instance->config.bindaddr, bindaddr_str, sizeof(bindaddr_str))); - list_for_each_entry(client, &instance->clients, list) { - const char *s_client_evshut = redsocks_evshut_str(client->client_evshut); - const char *s_relay_evshut = redsocks_evshut_str(client->relay_evshut); +static void redsocks_dump_instance(redsocks_instance *instance) +{ + redsocks_client *client = NULL; + char addr_str[RED_INET_ADDRSTRLEN]; - redsocks_log_error(client, LOG_NOTICE, "client: %i (%s)%s%s, relay: %i (%s)%s%s, age: %li sec, idle: %li sec.", - client->client ? EVENT_FD(&client->client->ev_write) : -1, - client->client ? redsocks_event_str(client->client->enabled) : "NULL", - s_client_evshut[0] ? " " : "", s_client_evshut, - client->relay ? EVENT_FD(&client->relay->ev_write) : -1, - client->relay ? redsocks_event_str(client->relay->enabled) : "NULL", - s_relay_evshut[0] ? " " : "", s_relay_evshut, - now - client->first_event, - now - client->last_event); - } - log_error(LOG_NOTICE, "End of client list."); + log_error(LOG_INFO, "Dumping client list for instance (%s @ %s):", + instance->relay_ss->name, + red_inet_ntop(&instance->config.bindaddr, addr_str, sizeof(addr_str))); + list_for_each_entry(client, &instance->clients, list) + redsocks_dump_client(client, LOG_INFO); + + log_error(LOG_INFO, "End of client list."); } -static void redsocks_debug_dump(int sig, short what, void *_arg) +static void redsocks_debug_dump() { - redsocks_instance *instance = NULL; - time_t now = redsocks_time(NULL); + redsocks_instance *instance = NULL; + + list_for_each_entry(instance, &instances, list) + redsocks_dump_instance(instance); +} - list_for_each_entry(instance, &instances, list) - redsocks_debug_dump_instance(instance, now); +/* Audit is required to clean up hung connections. + * Not all connections are closed gracefully by both ends. In any case that + * either far end of client or far end of relay does not close connection + * gracefully, we got hung connections. + */ +static void redsocks_audit_instance(redsocks_instance *instance) +{ + redsocks_client *tmp, *client = NULL; + time_t now = redsocks_time(NULL); + int drop_it = 0; + char addr_str[RED_INET_ADDRSTRLEN]; + + log_error(LOG_DEBUG, "Audit client list for instance (%s @ %s):", + instance->relay_ss->name, + red_inet_ntop(&instance->config.bindaddr, addr_str, sizeof(addr_str))); + list_for_each_entry_safe(client, tmp, &instance->clients, list) { + drop_it = 0; + + if (now - client->last_event >= REDSOCKS_AUDIT_INTERVAL){ + /* Only take actions if no touch of the client for at least an audit cycle.*/ + /* drop this client if either end disconnected */ + if ((client->client_evshut == EV_WRITE && client->relay_evshut == EV_READ) + || (client->client_evshut == EV_READ && client->relay_evshut == EV_WRITE) + || (client->client_evshut == (EV_READ|EV_WRITE) && client->relay_evshut == EV_WRITE) + || (client->client_evshut == EV_READ && client->relay == NULL)) + drop_it = 1; + } + /* close long connections without activities */ + if (now - client->last_event >= 3600 * 2) + drop_it = 1; + + if (drop_it){ + redsocks_dump_client(client, LOG_DEBUG); + redsocks_drop_client(client); + } + } + log_error(LOG_DEBUG, "End of auditing client list."); +} + +static void redsocks_audit(int sig, short what, void *_arg) +{ + redsocks_instance * tmp, *instance = NULL; + + list_for_each_entry_safe(instance, tmp, &instances, list) + redsocks_audit_instance(instance); } static void redsocks_fini_instance(redsocks_instance *instance); static int redsocks_init_instance(redsocks_instance *instance) { - /* FIXME: redsocks_fini_instance is called in case of failure, this - * function will remove instance from instances list - result - * looks ugly. - */ - int error; - int on = 1; - int fd = -1; - - fd = socket(AF_INET, SOCK_STREAM, 0); - if (fd == -1) { - log_errno(LOG_ERR, "socket"); - goto fail; - } - - error = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); - if (error) { - log_errno(LOG_ERR, "setsockopt"); - goto fail; - } - - error = bind(fd, (struct sockaddr*)&instance->config.bindaddr, sizeof(instance->config.bindaddr)); - if (error) { - log_errno(LOG_ERR, "bind"); - goto fail; - } - - error = fcntl_nonblock(fd); - if (error) { - log_errno(LOG_ERR, "fcntl"); - goto fail; - } - - error = listen(fd, instance->config.listenq); - if (error) { - log_errno(LOG_ERR, "listen"); - goto fail; - } - - tracked_event_set(&instance->listener, fd, EV_READ | EV_PERSIST, redsocks_accept_client, instance); - fd = -1; - - tracked_event_set(&instance->accept_backoff, -1, 0, redsocks_accept_backoff, instance); - - error = tracked_event_add(&instance->listener, NULL); - if (error) { - log_errno(LOG_ERR, "event_add"); - goto fail; - } - - return 0; + /* FIXME: redsocks_fini_instance is called in case of failure, this + * function will remove instance from instances list - result + * looks ugly. + */ + int error; + int bindaddr_len = 0; + evutil_socket_t fd = -1; + + if (instance->relay_ss->instance_init + && instance->relay_ss->instance_init(instance)) { + log_errno(LOG_ERR, "Failed to init relay subsystem."); + goto fail; + } + + fd = socket(instance->config.bindaddr.ss_family, SOCK_STREAM, IPPROTO_TCP); + if (fd == -1) { + log_errno(LOG_ERR, "socket"); + goto fail; + } + + error = evutil_make_listen_socket_reuseable(fd); + if (error) { + log_errno(LOG_ERR, "evutil_make_listen_socket_reuseable"); + goto fail; + } + + // iptables TPROXY target does not send packets to non-transparent sockets + if (make_socket_transparent(fd)) + log_error(LOG_WARNING, "Continue without TPROXY support"); + + if (apply_reuseport(fd)) + log_error(LOG_WARNING, "Continue without SO_REUSEPORT enabled"); + +#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) + bindaddr_len = instance->config.bindaddr.ss_len > 0 ? instance->config.bindaddr.ss_len : sizeof(instance->config.bindaddr); +#else + bindaddr_len = sizeof(instance->config.bindaddr); +#endif + error = bind(fd, (struct sockaddr*)&instance->config.bindaddr, bindaddr_len); + if (error) { + log_errno(LOG_ERR, "bind"); + goto fail; + } + + error = evutil_make_socket_nonblocking(fd); + if (error) { + log_errno(LOG_ERR, "evutil_make_socket_nonblocking"); + goto fail; + } + + apply_tcp_fastopen(fd); + error = listen(fd, instance->config.listenq); + if (error) { + log_errno(LOG_ERR, "listen"); + goto fail; + } + + tracked_event_set(&instance->listener, fd, EV_READ | EV_PERSIST, redsocks_accept_client, instance); + fd = -1; + + tracked_event_set(&instance->accept_backoff, -1, 0, redsocks_accept_backoff, instance); + + error = tracked_event_add(&instance->listener, NULL); + if (error) { + log_errno(LOG_ERR, "event_add"); + goto fail; + } + + return 0; fail: - redsocks_fini_instance(instance); + redsocks_fini_instance(instance); - if (fd != -1) { - redsocks_close(fd); - } + if (fd != -1) { + redsocks_close(fd); + } - return -1; + return -1; } /* Drops instance completely, freeing its memory and removing from * instances list. */ static void redsocks_fini_instance(redsocks_instance *instance) { - if (!list_empty(&instance->clients)) { - redsocks_client *tmp, *client = NULL; - - log_error(LOG_WARNING, "There are connected clients during shutdown! Disconnecting them."); - list_for_each_entry_safe(client, tmp, &instance->clients, list) { - redsocks_drop_client(client); - } - } - - if (instance->relay_ss->instance_fini) - instance->relay_ss->instance_fini(instance); - - if (event_initialized(&instance->listener.ev)) { - if (timerisset(&instance->listener.inserted)) - if (tracked_event_del(&instance->listener) != 0) - log_errno(LOG_WARNING, "event_del"); - redsocks_close(EVENT_FD(&instance->listener.ev)); - memset(&instance->listener, 0, sizeof(instance->listener)); - } - - if (event_initialized(&instance->accept_backoff.ev)) { - if (timerisset(&instance->accept_backoff.inserted)) - if (tracked_event_del(&instance->accept_backoff) != 0) - log_errno(LOG_WARNING, "event_del"); - memset(&instance->accept_backoff, 0, sizeof(instance->accept_backoff)); - } - - list_del(&instance->list); - - free(instance->config.type); - free(instance->config.login); - free(instance->config.password); - - memset(instance, 0, sizeof(*instance)); - free(instance); + if (!list_empty(&instance->clients)) { + redsocks_client *tmp, *client = NULL; + + log_error(LOG_WARNING, "There are connected clients during shutdown! Disconnecting them."); + list_for_each_entry_safe(client, tmp, &instance->clients, list) { + redsocks_drop_client(client); + } + } + + if (instance->relay_ss->instance_fini) + instance->relay_ss->instance_fini(instance); + + if (instance->listener.ev) { + int fd = event_get_fd(instance->listener.ev); + tracked_event_free(&instance->listener); + redsocks_close(fd); + memset(&instance->listener, 0, sizeof(instance->listener)); + } + tracked_event_free(&instance->accept_backoff); + memset(&instance->accept_backoff, 0, sizeof(instance->accept_backoff)); + + list_del(&instance->list); + + free(instance->config.type); + free(instance->config.login); + free(instance->config.password); + free(instance->config.interface); + + memset(instance, 0, sizeof(*instance)); + free(instance); } static int redsocks_fini(); -static struct event debug_dumper; +static struct event * audit_event = NULL; static int redsocks_init() { - struct sigaction sa = { }, sa_old = { }; - redsocks_instance *tmp, *instance = NULL; - - sa.sa_handler = SIG_IGN; - sa.sa_flags = SA_RESTART; - if (sigaction(SIGPIPE, &sa, &sa_old) == -1) { - log_errno(LOG_ERR, "sigaction"); - return -1; - } - - signal_set(&debug_dumper, SIGUSR1, redsocks_debug_dump, NULL); - if (signal_add(&debug_dumper, NULL) != 0) { - log_errno(LOG_ERR, "signal_add"); - goto fail; - } - - list_for_each_entry_safe(instance, tmp, &instances, list) { - if (redsocks_init_instance(instance) != 0) - goto fail; - } - - return 0; + redsocks_instance *tmp, *instance = NULL; + struct timeval audit_time; + struct event_base * base = get_event_base(); + + /* Start audit */ + audit_time.tv_sec = REDSOCKS_AUDIT_INTERVAL; + audit_time.tv_usec = 0; + audit_event = event_new(base, -1, EV_TIMEOUT|EV_PERSIST, redsocks_audit, NULL); + if (!audit_event) + goto fail; + if (evtimer_add(audit_event, &audit_time)) + goto fail; + + list_for_each_entry_safe(instance, tmp, &instances, list) { + if (redsocks_init_instance(instance) != 0) + goto fail; + } + + return 0; fail: - // that was the first resource allocation, it return's on failure, not goto-fail's - sigaction(SIGPIPE, &sa_old, NULL); + // that was the first resource allocation, it return's on failure, not goto-fail's + redsocks_fini(); - redsocks_fini(); - - return -1; + return -1; } static int redsocks_fini() { - redsocks_instance *tmp, *instance = NULL; - - list_for_each_entry_safe(instance, tmp, &instances, list) - redsocks_fini_instance(instance); - - if (signal_initialized(&debug_dumper)) { - if (signal_del(&debug_dumper) != 0) - log_errno(LOG_WARNING, "signal_del"); - memset(&debug_dumper, 0, sizeof(debug_dumper)); - } - - return 0; + redsocks_instance *tmp, *instance = NULL; + + list_for_each_entry_safe(instance, tmp, &instances, list) + redsocks_fini_instance(instance); + + /* stop audit */ + if (audit_event) { + evtimer_del(audit_event); + event_free(audit_event); + audit_event = NULL; + } + return 0; } app_subsys redsocks_subsys = { - .init = redsocks_init, - .fini = redsocks_fini, - .conf_section = &redsocks_conf_section, + .init = redsocks_init, + .fini = redsocks_fini, + .dump = redsocks_debug_dump, + .conf_section = &redsocks_conf_section, }; diff --git a/redsocks.conf.example b/redsocks.conf.example index ffec3dca..d93532ac 100644 --- a/redsocks.conf.example +++ b/redsocks.conf.example @@ -1,5 +1,5 @@ base { - // debug: connection progress + // debug: connection progress & client list on SIGUSR1 log_debug = off; // info: start and end of client session @@ -21,6 +21,9 @@ base { * privilegies on startup. * Note, your chroot may requre /etc/localtime if you write log to syslog. * Log is opened before chroot & uid changing. + * Debian, Ubuntu and some other distributions use `nogroup` instead of + * `nobody`, so change it according to your system if you want redsocks + * to drop root privileges. */ // user = nobody; // group = nobody; @@ -40,15 +43,21 @@ base { //tcp_keepalive_time = 0; //tcp_keepalive_probes = 0; //tcp_keepalive_intvl = 0; + + /* Enable or disable Linux 3.9+ specific socket option SO_REUSEPORT. + * Some older versions of Linux like CentOS 6.5 (Kernel 2.6.32) also + # support this option. + * Default to off. + */ + reuseport = off; } redsocks { - /* `local_ip' defaults to 127.0.0.1 for security reasons, + /* `bind' defaults to 127.0.0.1:0 for security reasons, * use 0.0.0.0 if you want to listen on every interface. - * `local_*' are used as port to redirect to. + * `bind' are used as ip:port to redirect to. */ - local_ip = 127.0.0.1; - local_port = 12345; + bind = "127.0.0.1:12345"; // listen() queue length. Default value is SOMAXCONN and it should be // good enough for most of us. @@ -62,54 +71,125 @@ redsocks { // min_accept_backoff = 100; // max_accept_backoff = 60000; - // `ip' and `port' are IP and tcp-port of proxy-server - // You can also use hostname instead of IP, only one (random) - // address of multihomed host will be used. - ip = example.org; - port = 1080; - + // `relay` is IP address and port of proxy-server. + // Can be: + // [IPv6Address]:port + // [IPv6Address] + // IPv6Address + // IPv4Address:port + // IPv4Address + // domain.name:port + // If no port is given, 0 is used. Usually, a valid port is required. + // Random valid resolved IP address will be used. + relay = "127.0.0.1:1080"; // known types: socks4, socks5, http-connect, http-relay + // New types: direct, shadowsocks, https-connect + // For type direct: + // if `relay` is not specified, connections will be forwarded to + // original destinations. + // if `relay` is filled with valid IP address, connections will be + // forwarded to IP address defined in `relay`. It is useful when you + // just want to forward connections to a specific IP address without + // transparent proxy. E.g. forward IPv4:port to IPv6:port. type = socks5; - // login = "foobar"; + // Specify interface for outgoing connections. + // This is useful when you have multiple connections to + // internet or when you have VPN connections. + // interface = tun0; + + // Change this parameter to 1 if you want auto proxy feature. + // When autoproxy is set to non-zero, the connection to target + // will be made directly first. If direct connection to target + // fails for timeout/connection refuse, redsocks will try to + // connect to target via the proxy. + autoproxy = 0; + // timeout is meaningful when 'autoproxy' is non-zero. + // It specified timeout value when trying to connect to destination + // directly. Default is 10 seconds. When it is set to 0, default + // timeout value will be used. + timeout = 10; + + // login = "foobar";// field 'login' is reused as encryption + // method of shadowsocks // password = "baz"; } redudp { - // `local_ip' should not be 0.0.0.0 as it's also used for outgoing + // `bind' should not be 0.0.0.0:0 as it's also used for outgoing // packets that are sent as replies - and it should be fixed // if we want NAT to work properly. - local_ip = 127.0.0.1; - local_port = 10053; + bind = "127.0.0.1:10053"; - // `ip' and `port' of socks5 proxy server. - ip = 10.0.0.1; - port = 1080; - login = username; + // `relay' is ip and port of socks5 proxy server. + relay = "10.0.0.1:1080"; + login = username;// field 'login' is reused as encryption + // method of shadowsocks password = pazzw0rd; + // know types: socks5, shadowsocks + type = socks5; + // redsocks knows about two options while redirecting UDP packets at // linux: TPROXY and REDIRECT. TPROXY requires more complex routing // configuration and fresh kernel (>= 2.6.37 according to squid // developers[1]) but has hack-free way to get original destination - // address, REDIRECT is easier to configure, but requires `dest_ip` and - // `dest_port` to be set, limiting packet redirection to single - // destination. + // address, REDIRECT is easier to configure, but requires `dest` + // to be set, limiting packet redirection to single destination. // [1] http://wiki.squid-cache.org/Features/Tproxy4 - dest_ip = 8.8.8.8; - dest_port = 53; + dest = "8.8.8.8:53"; + // Do not set it large if this section is for DNS requests. Otherwise, + // you may encounter out of file descriptor problem. For DNS requests, + // 10s is adequate. udp_timeout = 30; - udp_timeout_stream = 180; + // udp_timeout_stream = 180; } -dnstc { - // fake and really dumb DNS server that returns "truncated answer" to - // every query via UDP, RFC-compliant resolver should repeat same query - // via TCP in this case. - local_ip = 127.0.0.1; - local_port = 5300; +tcpdns { + // Transform UDP DNS requests into TCP DNS requests. + // You can also redirect connections to external TCP DNS server to + // REDSOCKS transparent proxy via iptables. + bind = "192.168.1.1:1053"; // Local server to act as DNS server + tcpdns1 = "8.8.4.4:53"; // DNS server that supports TCP DNS requests + tcpdns2 = "8.8.8.8" ; // DNS server that supports TCP DNS requests + timeout = 4; // Timeout value for TCP DNS requests +} + +autoproxy { + // Specify interface for outgoing connections. + // This is useful when you have multiple connections to + // internet or when you have VPN connections. + // interface = wlan0; + + no_quick_check_seconds = 60; // Directly relay traffic to proxy if an IP + // is found blocked in cache and it has been + // added into cache no earlier than this + // specified number of seconds. + // Set it to 0 if you do not want to perform + // quick check when an IP is found in blocked + // IP cache, thus the connection will be + // redirected to proxy immediately. + quick_connect_timeout = 3; // Timeout value when performing quick + // connection check if an IP is found blocked + // in cache. +} + +ipcache { + // Configure IP cache + cache_size = 4; // Maximum number of IP's in 1K. + stale_time = 900; // Seconds to stale an IP in cache since it is added + // into cache. + // Set it to 0 to disable cache stale. + port_check = 1; // Whether to distinguish port number in address + cache_file = "/tmp/ipcache.txt"; // File used to store blocked IP's in cache. + autosave_interval = 3600; // Interval for saving ip cache into file. + // Set it to 0 to disable autosave. + // When autosave_interval and stale_time are both 0, IP cache behaves like + // a static blacklist. } // you can add more `redsocks' and `redudp' sections if you need. + + diff --git a/redsocks.h b/redsocks.h index 4f072d56..1437dfb1 100644 --- a/redsocks.h +++ b/redsocks.h @@ -3,10 +3,12 @@ #include #include #include -#include +#include #include "list.h" +#define DEFAULT_CONNECT_TIMEOUT 10 + struct redsocks_client_t; struct redsocks_instance_t; @@ -14,28 +16,36 @@ typedef struct relay_subsys_t { char *name; size_t payload_len; // size of relay-specific data in client section size_t instance_payload_len; // size of relay-specify data in instance section - evbuffercb readcb; - evbuffercb writecb; + bufferevent_data_cb readcb; + bufferevent_data_cb writecb; void (*init)(struct redsocks_client_t *client); void (*fini)(struct redsocks_client_t *client); + int (*instance_init)(struct redsocks_instance_t *instance); void (*instance_fini)(struct redsocks_instance_t *instance); // connect_relay (if any) is called instead of redsocks_connect_relay after client connection acceptance - void (*connect_relay)(struct redsocks_client_t *client); + // It must returns 0 on success, returns -1 or error code on failures. + int (*connect_relay)(struct redsocks_client_t *client); + //void (*relay_connected)(struct redsocks_client_t *client); } relay_subsys; typedef struct redsocks_config_t { - struct sockaddr_in bindaddr; - struct sockaddr_in relayaddr; + struct sockaddr_storage bindaddr; + struct sockaddr_storage relayaddr; + char *bind; + char *relay; char *type; char *login; char *password; uint16_t min_backoff_ms; uint16_t max_backoff_ms; // backoff capped by 65 seconds is enough :) uint16_t listenq; + uint16_t autoproxy; + uint16_t timeout; + char *interface;// interface of relay } redsocks_config; struct tracked_event { - struct event ev; + struct event * ev; struct timeval inserted; }; @@ -54,9 +64,10 @@ typedef struct redsocks_client_t { redsocks_instance *instance; struct bufferevent *client; struct bufferevent *relay; - struct sockaddr_in clientaddr; - struct sockaddr_in destaddr; + struct sockaddr_storage clientaddr; + struct sockaddr_storage destaddr; int state; // it's used by bottom layer + short relay_connected; unsigned short client_evshut; unsigned short relay_evshut; time_t first_event; @@ -66,8 +77,10 @@ typedef struct redsocks_client_t { void redsocks_drop_client(redsocks_client *client); void redsocks_touch_client(redsocks_client *client); -void redsocks_connect_relay(redsocks_client *client); -void redsocks_start_relay(redsocks_client *client); +int redsocks_connect_relay(redsocks_client *client); +int redsocks_start_relay(redsocks_client *client); +void redsocks_dump_client(redsocks_client * client, int loglevel); +void redsocks_shutdown(redsocks_client *client, struct bufferevent *buffev, int how, int pseudo); typedef int (*size_comparator)(size_t a, size_t b); int sizes_equal(size_t a, size_t b); @@ -95,17 +108,20 @@ int redsocks_write_helper( void redsocks_close_internal(int fd, const char* file, int line, const char *func); #define redsocks_log_error(client, prio, msg...) \ - redsocks_log_write_plain(__FILE__, __LINE__, __func__, 0, &(client)->clientaddr, &(client)->destaddr, prio, ## msg) + redsocks_log_write_plain(__FILE__, __LINE__, __func__, 0, (struct sockaddr_storage *)&(client)->clientaddr, (struct sockaddr_storage *)&(client)->destaddr, prio, ## msg) #define redsocks_log_errno(client, prio, msg...) \ - redsocks_log_write_plain(__FILE__, __LINE__, __func__, 1, &(client)->clientaddr, &(client)->destaddr, prio, ## msg) + redsocks_log_write_plain(__FILE__, __LINE__, __func__, 1, (struct sockaddr_storage *)&(client)->clientaddr, (struct sockaddr_storage *)&(client)->destaddr, prio, ## msg) void redsocks_log_write_plain( const char *file, int line, const char *func, int do_errno, - const struct sockaddr_in *clientaddr, const struct sockaddr_in *destaddr, + const struct sockaddr_storage *clientaddr, const struct sockaddr_storage *destaddr, int priority, const char *fmt, ...) #if defined(__GNUC__) __attribute__ (( format (printf, 8, 9) )) #endif ; +/* unsafe internal functions. Only use them when you know exactly what +you are doing with */ +int process_shutdown_on_write_(redsocks_client *client, struct bufferevent *from, struct bufferevent *to); /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ diff --git a/redsocks.service b/redsocks.service deleted file mode 100644 index 2913b7d9..00000000 --- a/redsocks.service +++ /dev/null @@ -1,16 +0,0 @@ -[Unit] -Description=Transparent redirector of any TCP connection to proxy using your firewall - -[Service] -Type=forking -PIDFile=/run/redsocks/redsocks.pid -EnvironmentFile=/etc/conf.d/redsocks -User=redsocks -ExecStartPre=/usr/bin/redsocks -t -c $REDSOCKS_CONF -ExecStart=/usr/bin/redsocks -c $REDSOCKS_CONF \ - -p /run/redsocks/redsocks.pid -ExecStopPost=/bin/rm /run/redsocks/redsocks.pid -Restart=on-abort - -[Install] -WantedBy=multi-user.target diff --git a/redsocks2.service b/redsocks2.service new file mode 100644 index 00000000..d7edf9c7 --- /dev/null +++ b/redsocks2.service @@ -0,0 +1,16 @@ +[Unit] +Description=Transparent redirector of any TCP connection to proxy using your firewall + +[Service] +Type=forking +PIDFile=/run/redsocks2/redsocks2.pid +EnvironmentFile=/etc/conf.d/redsocks2 +User=redsocks +ExecStartPre=/usr/bin/redsocks2 -t -c $REDSOCKS_CONF +ExecStart=/usr/bin/redsocks2 -c $REDSOCKS_CONF \ + -p /run/redsocks2/redsocks2.pid +ExecStopPost=/bin/rm /run/redsocks2/redsocks2.pid +Restart=on-abort + +[Install] +WantedBy=multi-user.target diff --git a/redudp.c b/redudp.c index 05460dcd..50947416 100644 --- a/redudp.c +++ b/redudp.c @@ -1,4 +1,12 @@ -/* redsocks - transparent TCP-to-proxy redirector + +/* redsocks2 - transparent TCP/UDP-to-proxy redirector + * Copyright (C) 2013-2017 Zhuofei Wang + * + * This code is based on redsocks project developed by Leonid Evdokimov. + * Licensed under the Apache License, Version 2.0 (the "License"). + * + * + * redsocks - transparent TCP-to-proxy redirector * Copyright (C) 2007-2011 Leonid Evdokimov * * Licensed under the Apache License, Version 2.0 (the "License"); you may not @@ -25,6 +33,7 @@ #include #include +#include "base.h" #include "list.h" #include "log.h" #include "socks5.h" @@ -34,638 +43,445 @@ #include "redudp.h" #include "libc-compat.h" -#define redudp_log_error(client, prio, msg...) \ - redsocks_log_write_plain(__FILE__, __LINE__, __func__, 0, &(client)->clientaddr, get_destaddr(client), prio, ## msg) -#define redudp_log_errno(client, prio, msg...) \ - redsocks_log_write_plain(__FILE__, __LINE__, __func__, 1, &(client)->clientaddr, get_destaddr(client), prio, ## msg) +#define DEFAULT_MAX_PKTQUEUE 5 +#define DEFAULT_UDP_TIMEOUT 30 +#define REDUDP_AUDIT_INTERVAL 10 + +// Multiple instances share the same buffer for message receiving +static char shared_buff[MAX_UDP_PACKET_SIZE];// max size of UDP packet is less than 64K -static void redudp_pkt_from_socks(int fd, short what, void *_arg); -static void redudp_drop_client(redudp_client *client); static void redudp_fini_instance(redudp_instance *instance); static int redudp_fini(); -static int redudp_transparent(int fd); - -typedef struct redudp_expected_assoc_reply_t { - socks5_reply h; - socks5_addr_ipv4 ip; -} PACKED redudp_expected_assoc_reply; -struct bound_udp4_key { - struct in_addr sin_addr; - uint16_t sin_port; +struct bound_udp_key { + int sa_family; + union { + uint16_t sin_port; + uint16_t sin6_port; + }; + // Do not change position + union { + struct in_addr sin_addr; + struct in6_addr sin6_addr; + }; }; -struct bound_udp4 { - struct bound_udp4_key key; - int ref; - int fd; +struct bound_udp { + struct bound_udp_key key; + int ref; + int fd; + time_t t_last_rx; }; +extern udprelay_subsys socks5_udp_subsys; +#if !defined(DISABLE_SHADOWSOCKS) +extern udprelay_subsys shadowsocks_udp_subsys; +#endif +static udprelay_subsys *relay_subsystems[] = +{ + &socks5_udp_subsys, + #if !defined(DISABLE_SHADOWSOCKS) + &shadowsocks_udp_subsys, + #endif +}; /*********************************************************************** * Helpers */ // TODO: separate binding to privileged process (this operation requires uid-0) -static void* root_bound_udp4 = NULL; // to avoid two binds to same IP:port +static void* root_bound_udp = NULL; // to avoid two binds to same IP:port -static int bound_udp4_cmp(const void *a, const void *b) +static int bound_udp_cmp(const void *a, const void *b) { - return memcmp(a, b, sizeof(struct bound_udp4_key)); + return memcmp(a, b, sizeof(struct bound_udp_key)); } -static void bound_udp4_mkkey(struct bound_udp4_key *key, const struct sockaddr_in *addr) -{ - memset(key, 0, sizeof(*key)); - key->sin_addr = addr->sin_addr; - key->sin_port = addr->sin_port; +static void bound_udp_mkkey(struct bound_udp_key *key, const struct sockaddr *addr) +{ + memset(key, 0, sizeof(*key)); + key->sa_family = addr->sa_family; + if (addr->sa_family == AF_INET) { + key->sin_addr = ((const struct sockaddr_in*)addr)->sin_addr; + key->sin_port = ((const struct sockaddr_in*)addr)->sin_port; + } + else if (addr->sa_family == AF_INET6) { + key->sin6_addr = ((const struct sockaddr_in6*)addr)->sin6_addr; + key->sin6_port = ((const struct sockaddr_in6*)addr)->sin6_port; + } } -static int bound_udp4_get(const struct sockaddr_in *addr) -{ - struct bound_udp4_key key; - struct bound_udp4 *node, **pnode; - - bound_udp4_mkkey(&key, addr); - // I assume, that memory allocation for lookup is awful, so I use - // tfind/tsearch pair instead of tsearch/check-result. - pnode = tfind(&key, &root_bound_udp4, bound_udp4_cmp); - if (pnode) { - assert((*pnode)->ref > 0); - (*pnode)->ref++; - return (*pnode)->fd; - } - - node = calloc(1, sizeof(*node)); - if (!node) { - log_errno(LOG_ERR, "calloc"); - goto fail; - } - - node->key = key; - node->ref = 1; - node->fd = socket(AF_INET, SOCK_DGRAM, 0); - if (node->fd == -1) { - log_errno(LOG_ERR, "socket"); - goto fail; - } - - if (0 != redudp_transparent(node->fd)) - goto fail; - - if (0 != bind(node->fd, (struct sockaddr*)addr, sizeof(*addr))) { - log_errno(LOG_ERR, "bind"); - goto fail; - } - - pnode = tsearch(node, &root_bound_udp4, bound_udp4_cmp); - if (!pnode) { - log_errno(LOG_ERR, "tsearch(%p) == %p", node, pnode); - goto fail; - } - assert(node == *pnode); - - return node->fd; +static int bound_udp_get(const struct sockaddr *addr) +{ + struct bound_udp_key key; + struct bound_udp *node, **pnode; + + bound_udp_mkkey(&key, addr); + // I assume, that memory allocation for lookup is awful, so I use + // tfind/tsearch pair instead of tsearch/check-result. + pnode = tfind(&key, &root_bound_udp, bound_udp_cmp); + if (pnode) { + assert((*pnode)->ref > 0); + (*pnode)->ref++; + (*pnode)->t_last_rx = redsocks_time(NULL); + return (*pnode)->fd; + } + + node = calloc(1, sizeof(*node)); + if (!node) { + log_errno(LOG_ERR, "calloc"); + goto fail; + } + + node->key = key; + node->ref = 1; + node->fd = socket(addr->sa_family, SOCK_DGRAM, IPPROTO_UDP); +#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) + int on = 1; +#if defined(__FreeBSD__) +#ifdef SOL_IPV6 + if (addr->sa_family == AF_INET) { +#endif + setsockopt(node->fd, IPPROTO_IP, IP_BINDANY, &on, sizeof(on)); +#ifdef SOL_IPV6 + } else { + setsockopt(node->fd, IPPROTO_IPV6, IPV6_BINDANY, &on, sizeof(on)); + } +#endif +#else + setsockopt(node->fd, SOL_SOCKET, SO_BINDANY, &on, sizeof(on)); +#endif +#endif + node->t_last_rx = redsocks_time(NULL); + if (node->fd == -1) { + log_errno(LOG_ERR, "socket"); + goto fail; + } + + if (0 != make_socket_transparent(node->fd)) + goto fail; + + if (evutil_make_listen_socket_reuseable(node->fd)) { + log_errno(LOG_ERR, "evutil_make_listen_socket_reuseable"); + goto fail; + } + + socklen_t bindaddr_len; +#ifdef SOL_IPV6 + if (addr->sa_family == AF_INET) { +#endif + bindaddr_len = sizeof(struct sockaddr_in); +#ifdef SOL_IPV6 + } else { + bindaddr_len = sizeof(struct sockaddr_in6); + } +#endif + if (0 != bind(node->fd, (struct sockaddr*)addr, bindaddr_len)) { + log_errno(LOG_ERR, "bind"); + goto fail; + } + + if (0 != evutil_make_socket_nonblocking(node->fd)) { + log_errno(LOG_ERR, "evutil_make_socket_nonblocking"); + goto fail; + } + + pnode = tsearch(node, &root_bound_udp, bound_udp_cmp); + if (!pnode) { + log_errno(LOG_ERR, "tsearch(%p) == %p", node, pnode); + goto fail; + } + assert(node == *pnode); + + return node->fd; fail: - if (node) { - if (node->fd != -1) - redsocks_close(node->fd); - free(node); - } - return -1; + if (node) { + if (node->fd != -1) + close(node->fd); + free(node); + } + return -1; } -static void bound_udp4_put(const struct sockaddr_in *addr) +static void bound_udp_put(const struct sockaddr *addr) { - struct bound_udp4_key key; - struct bound_udp4 **pnode, *node; - void *parent; + struct bound_udp_key key; + struct bound_udp **pnode, *node; + void *parent; - bound_udp4_mkkey(&key, addr); - pnode = tfind(&key, &root_bound_udp4, bound_udp4_cmp); - assert(pnode && (*pnode)->ref > 0); + bound_udp_mkkey(&key, addr); + pnode = tfind(&key, &root_bound_udp, bound_udp_cmp); + assert(pnode && (*pnode)->ref > 0); - node = *pnode; + node = *pnode; - node->ref--; - if (node->ref) - return; + node->ref--; + if (node->ref) + return; - parent = tdelete(node, &root_bound_udp4, bound_udp4_cmp); - assert(parent); + parent = tdelete(node, &root_bound_udp, bound_udp_cmp); + assert(parent); - redsocks_close(node->fd); // expanding `pnode` to avoid use after free - free(node); + close(node->fd); // expanding `pnode` to avoid use after free + free(node); } -static int redudp_transparent(int fd) -{ - int on = 1; - int error = setsockopt(fd, SOL_IP, IP_TRANSPARENT, &on, sizeof(on)); - if (error) - log_errno(LOG_ERR, "setsockopt(..., SOL_IP, IP_TRANSPARENT)"); - return error; +/* + * This procedure is ued to audit tree items for destination addresses. + * For each destination address, if no packet received from it for a certain period, + * it is removed and the corresponding FD is closed. + */ +static void bound_udp_action(const void *nodep, const VISIT which, const int depth) +{ + time_t now; + struct bound_udp *datap; + void *parent; + char buf[RED_INET_ADDRSTRLEN]; + + switch (which) { + case preorder: + case postorder: + break; + case endorder: + case leaf: + now = redsocks_time(NULL); + datap = *(struct bound_udp **) nodep; + // TODO: find a proper way to make timeout configurable for each instance. + if (datap->t_last_rx + 20 < now) { + parent = tdelete(datap, &root_bound_udp, bound_udp_cmp); + assert(parent); + + inet_ntop(datap->key.sa_family, &datap->key.sin_addr, &buf[0], sizeof(buf)); + log_error(LOG_DEBUG, "Close UDP socket %d to %s:%u", datap->fd, + &buf[0], ntohs(datap->key.sin_port)); + close(datap->fd); + free(datap); + } + break; + } } static int do_tproxy(redudp_instance* instance) { - return instance->config.destaddr.sin_addr.s_addr == 0; + return instance->config.dest == NULL; } -static struct sockaddr_in* get_destaddr(redudp_client *client) +struct sockaddr_storage* get_destaddr(redudp_client *client) { - if (do_tproxy(client->instance)) - return &client->destaddr; - else - return &client->instance->config.destaddr; -} - -static void redudp_fill_preamble(socks5_udp_preabmle *preamble, redudp_client *client) -{ - preamble->reserved = 0; - preamble->frag_no = 0; /* fragmentation is not supported */ - preamble->addrtype = socks5_addrtype_ipv4; - preamble->ip.addr = get_destaddr(client)->sin_addr.s_addr; - preamble->ip.port = get_destaddr(client)->sin_port; -} - -static struct evbuffer* socks5_mkmethods_plain_wrapper(void *p) -{ - int *do_password = p; - return socks5_mkmethods_plain(*do_password); -} - -static struct evbuffer* socks5_mkpassword_plain_wrapper(void *p) -{ - redudp_instance *self = p; - return socks5_mkpassword_plain(self->config.login, self->config.password); -} - -static struct evbuffer* socks5_mkassociate(void *p) -{ - struct sockaddr_in sa; - p = p; /* Make compiler happy */ - memset(&sa, 0, sizeof(sa)); - sa.sin_family = AF_INET; - return socks5_mkcommand_plain(socks5_cmd_udp_associate, &sa); + if (do_tproxy(client->instance)) + return &client->destaddr; + else + return &client->instance->config.destaddr; } /*********************************************************************** * Logic */ -static void redudp_drop_client(redudp_client *client) -{ - int fd; - redudp_log_error(client, LOG_INFO, "Dropping..."); - enqueued_packet *q, *tmp; - if (event_initialized(&client->timeout)) { - if (event_del(&client->timeout) == -1) - redudp_log_errno(client, LOG_ERR, "event_del"); - } - if (client->relay) { - fd = EVENT_FD(&client->relay->ev_read); - bufferevent_free(client->relay); - shutdown(fd, SHUT_RDWR); - redsocks_close(fd); - } - if (event_initialized(&client->udprelay)) { - fd = EVENT_FD(&client->udprelay); - if (event_del(&client->udprelay) == -1) - redudp_log_errno(client, LOG_ERR, "event_del"); - redsocks_close(fd); - } - if (client->sender_fd != -1) - bound_udp4_put(&client->destaddr); - list_for_each_entry_safe(q, tmp, &client->queue, list) { - list_del(&q->list); - free(q); - } - list_del(&client->list); - free(client); -} - -static void redudp_bump_timeout(redudp_client *client) -{ - struct timeval tv; - tv.tv_sec = client->instance->config.udp_timeout; - tv.tv_usec = 0; - // TODO: implement udp_timeout_stream - if (event_add(&client->timeout, &tv) != 0) { - redudp_log_error(client, LOG_WARNING, "event_add(&client->timeout, ...)"); - redudp_drop_client(client); - } -} - -static void redudp_forward_pkt(redudp_client *client, char *buf, size_t pktlen) -{ - socks5_udp_preabmle req; - struct msghdr msg; - struct iovec io[2]; - ssize_t outgoing, fwdlen = pktlen + sizeof(req); - - redudp_fill_preamble(&req, client); - - memset(&msg, 0, sizeof(msg)); - msg.msg_name = &client->udprelayaddr; - msg.msg_namelen = sizeof(client->udprelayaddr); - msg.msg_iov = io; - msg.msg_iovlen = SIZEOF_ARRAY(io); - - io[0].iov_base = &req; - io[0].iov_len = sizeof(req); - io[1].iov_base = buf; - io[1].iov_len = pktlen; - - outgoing = sendmsg(EVENT_FD(&client->udprelay), &msg, 0); - if (outgoing == -1) { - redudp_log_errno(client, LOG_WARNING, "sendmsg: Can't forward packet, dropping it"); - return; - } - else if (outgoing != fwdlen) { - redudp_log_error(client, LOG_WARNING, "sendmsg: I was sending %zd bytes, but only %zd were sent.", fwdlen, outgoing); - return; - } +void redudp_drop_client(redudp_client *client) +{ + redudp_log_error(client, LOG_DEBUG, "Dropping client @ state: %d", client->state); + enqueued_packet *q, *tmp; + + if (client->instance->relay_ss->fini) + client->instance->relay_ss->fini(client); + + if (client->timeoutev) { + if (evtimer_del(client->timeoutev) == -1) + redudp_log_errno(client, LOG_ERR, "event_del"); + event_free(client->timeoutev); + } + list_for_each_entry_safe(q, tmp, &client->queue, list) { + list_del(&q->list); + free(q); + } + list_del(&client->list); + free(client); } -static int redudp_enqeue_pkt(redudp_client *client, char *buf, size_t pktlen) +void redudp_bump_timeout(redudp_client *client) { - enqueued_packet *q = NULL; - - redudp_log_error(client, LOG_DEBUG, ""); - - if (client->queue_len >= client->instance->config.max_pktqueue) { - redudp_log_error(client, LOG_WARNING, "There are already %u packets in queue. Dropping.", - client->queue_len); - return -1; - } - - q = calloc(1, sizeof(enqueued_packet) + pktlen); - if (!q) { - redudp_log_errno(client, LOG_ERR, "Can't enqueue packet: calloc"); - return -1; - } - - q->len = pktlen; - memcpy(q->data, buf, pktlen); - client->queue_len += 1; - list_add_tail(&q->list, &client->queue); - return 0; + struct timeval tv; + tv.tv_sec = client->instance->config.udp_timeout; + tv.tv_usec = 0; + // TODO: implement udp_timeout_stream + if (event_add(client->timeoutev, &tv) != 0) { + redudp_log_error(client, LOG_WARNING, "event_add(&client->timeoutev, ...)"); + redudp_drop_client(client); + } } -static void redudp_flush_queue(redudp_client *client) -{ - enqueued_packet *q, *tmp; - redudp_log_error(client, LOG_INFO, "Starting UDP relay"); - list_for_each_entry_safe(q, tmp, &client->queue, list) { - redudp_forward_pkt(client, q->data, q->len); - list_del(&q->list); - free(q); - } - client->queue_len = 0; - assert(list_empty(&client->queue)); -} - -static void redudp_read_assoc_reply(struct bufferevent *buffev, void *_arg) -{ - redudp_client *client = _arg; - redudp_expected_assoc_reply reply; - int read = evbuffer_remove(buffev->input, &reply, sizeof(reply)); - int fd = -1; - int error; - redudp_log_error(client, LOG_DEBUG, ""); - - if (read != sizeof(reply)) { - redudp_log_errno(client, LOG_NOTICE, "evbuffer_remove returned only %i bytes instead of expected %zu", - read, sizeof(reply)); - goto fail; - } - - if (reply.h.ver != socks5_ver) { - redudp_log_error(client, LOG_NOTICE, "Socks5 server reported unexpected reply version: %u", reply.h.ver); - goto fail; - } - - if (reply.h.status != socks5_status_succeeded) { - redudp_log_error(client, LOG_NOTICE, "Socks5 server status: \"%s\" (%i)", - socks5_status_to_str(reply.h.status), reply.h.status); - goto fail; - } - - if (reply.h.addrtype != socks5_addrtype_ipv4) { - redudp_log_error(client, LOG_NOTICE, "Socks5 server reported unexpected address type for UDP dgram destination: %u", - reply.h.addrtype); - goto fail; - } - - client->udprelayaddr.sin_family = AF_INET; - client->udprelayaddr.sin_port = reply.ip.port; - client->udprelayaddr.sin_addr.s_addr = reply.ip.addr; - - fd = socket(AF_INET, SOCK_DGRAM, 0); - if (fd == -1) { - redudp_log_errno(client, LOG_ERR, "socket"); - goto fail; - } - - error = connect(fd, (struct sockaddr*)&client->udprelayaddr, sizeof(client->udprelayaddr)); - if (error) { - redudp_log_errno(client, LOG_NOTICE, "connect"); - goto fail; - } - - event_set(&client->udprelay, fd, EV_READ | EV_PERSIST, redudp_pkt_from_socks, client); - error = event_add(&client->udprelay, NULL); - if (error) { - redudp_log_errno(client, LOG_ERR, "event_add"); - goto fail; - } - - redudp_flush_queue(client); - // TODO: bufferevent_disable ? - - return; - -fail: - if (fd != -1) - redsocks_close(fd); - redudp_drop_client(client); -} - -static void redudp_read_auth_reply(struct bufferevent *buffev, void *_arg) -{ - redudp_client *client = _arg; - socks5_auth_reply reply; - int read = evbuffer_remove(buffev->input, &reply, sizeof(reply)); - int error; - redudp_log_error(client, LOG_DEBUG, ""); - - if (read != sizeof(reply)) { - redudp_log_errno(client, LOG_NOTICE, "evbuffer_remove returned only %i bytes instead of expected %zu", - read, sizeof(reply)); - goto fail; - } - - if (reply.ver != socks5_password_ver || reply.status != socks5_password_passed) { - redudp_log_error(client, LOG_NOTICE, "Socks5 authentication error. Version: %u, error code: %u", - reply.ver, reply.status); - goto fail; - } - - error = redsocks_write_helper_ex_plain( - client->relay, NULL, socks5_mkassociate, NULL, 0, /* last two are ignored */ - sizeof(redudp_expected_assoc_reply), sizeof(redudp_expected_assoc_reply)); - if (error) - goto fail; - - client->relay->readcb = redudp_read_assoc_reply; - - return; - -fail: - redudp_drop_client(client); +void redudp_fwd_pkt_to_sender(redudp_client *client, void *buf, size_t len, + struct sockaddr_storage * srcaddr) +{ + size_t sent; + int fd; + redsocks_time(&client->last_relay_event); + redudp_bump_timeout(client); + + // When working with TPROXY, we have to get sender FD from tree on + // receipt of each packet from relay. + fd = do_tproxy(client->instance) + ? bound_udp_get((struct sockaddr*)srcaddr) : event_get_fd(client->instance->listener); + if (fd == -1) { + redudp_log_error(client, LOG_WARNING, "bound_udp_get failure"); + return; + } + // TODO: record remote address in client + + + sent = sendto(fd, buf, len, 0, +#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) + (struct sockaddr*)&client->clientaddr, sizeof(struct sockaddr_in)); +#else + (struct sockaddr*)&client->clientaddr, sizeof(client->clientaddr)); +#endif + if (sent != len) { + redudp_log_error( + client, + LOG_WARNING, + "sendto: I was sending %zd bytes, but only %zd were sent.", + len, + sent); + return; + } } -static void redudp_read_auth_methods(struct bufferevent *buffev, void *_arg) -{ - redudp_client *client = _arg; - int do_password = socks5_is_valid_cred(client->instance->config.login, client->instance->config.password); - socks5_method_reply reply; - int read = evbuffer_remove(buffev->input, &reply, sizeof(reply)); - const char *error = NULL; - int ierror = 0; - redudp_log_error(client, LOG_DEBUG, ""); - - if (read != sizeof(reply)) { - redudp_log_errno(client, LOG_NOTICE, "evbuffer_remove returned only %i bytes instead of expected %zu", - read, sizeof(reply)); - goto fail; - } - - error = socks5_is_known_auth_method(&reply, do_password); - if (error) { - redudp_log_error(client, LOG_NOTICE, "socks5_is_known_auth_method: %s", error); - goto fail; - } - else if (reply.method == socks5_auth_none) { - ierror = redsocks_write_helper_ex_plain( - client->relay, NULL, socks5_mkassociate, NULL, 0, /* last two are ignored */ - sizeof(redudp_expected_assoc_reply), sizeof(redudp_expected_assoc_reply)); - if (ierror) - goto fail; - - client->relay->readcb = redudp_read_assoc_reply; - } - else if (reply.method == socks5_auth_password) { - ierror = redsocks_write_helper_ex_plain( - client->relay, NULL, socks5_mkpassword_plain_wrapper, client->instance, 0, /* last one is ignored */ - sizeof(socks5_auth_reply), sizeof(socks5_auth_reply)); - if (ierror) - goto fail; - - client->relay->readcb = redudp_read_auth_reply; - } - - return; - -fail: - redudp_drop_client(client); +static int redudp_enqeue_pkt( + redudp_client *client, + struct sockaddr_storage * destaddr, + char *buf, + size_t pktlen) +{ + enqueued_packet *q = NULL; + + if (client->queue_len >= client->instance->config.max_pktqueue) { + redudp_log_error(client, LOG_WARNING, "There are already %u packets in queue. Dropping.", + client->queue_len); + return -1; + } + + q = malloc(sizeof(enqueued_packet) + pktlen); + if (!q) { + redudp_log_errno(client, LOG_ERR, "Can't enqueue packet: malloc"); + return -1; + } + + INIT_LIST_HEAD(&q->list); + memcpy(&q->destaddr, destaddr, sizeof(*destaddr)); + q->len = pktlen; + memcpy(q->data, buf, pktlen); + client->queue_len += 1; + list_add_tail(&q->list, &client->queue); + return 0; } -static void redudp_relay_connected(struct bufferevent *buffev, void *_arg) +void redudp_flush_queue(redudp_client *client) { - redudp_client *client = _arg; - int do_password = socks5_is_valid_cred(client->instance->config.login, client->instance->config.password); - int error; - char relayaddr_str[RED_INET_ADDRSTRLEN]; - redudp_log_error(client, LOG_DEBUG, "via %s", red_inet_ntop(&client->instance->config.relayaddr, relayaddr_str, sizeof(relayaddr_str))); - - if (!red_is_socket_connected_ok(buffev)) { - redudp_log_errno(client, LOG_NOTICE, "red_is_socket_connected_ok"); - goto fail; - } - - error = redsocks_write_helper_ex_plain( - client->relay, NULL, socks5_mkmethods_plain_wrapper, &do_password, 0 /* does not matter */, - sizeof(socks5_method_reply), sizeof(socks5_method_reply)); - if (error) - goto fail; - - client->relay->readcb = redudp_read_auth_methods; - client->relay->writecb = 0; - //bufferevent_disable(buffev, EV_WRITE); // I don't want to check for writeability. - return; + enqueued_packet *q, *tmp; + assert(client->instance->relay_ss->ready_to_fwd(client)); -fail: - redudp_drop_client(client); -} - -static void redudp_relay_error(struct bufferevent *buffev, short what, void *_arg) -{ - redudp_client *client = _arg; - // TODO: FIXME: Implement me - redudp_log_error(client, LOG_NOTICE, "redudp_relay_error"); - redudp_drop_client(client); + redudp_log_error(client, LOG_DEBUG, "Starting UDP relay"); + list_for_each_entry_safe(q, tmp, &client->queue, list) { + client->instance->relay_ss->forward_pkt(client, (struct sockaddr *)&q->destaddr, q->data, q->len); + list_del(&q->list); + free(q); + } + client->queue_len = 0; + assert(list_empty(&client->queue)); } static void redudp_timeout(int fd, short what, void *_arg) { - redudp_client *client = _arg; - redudp_log_error(client, LOG_INFO, "Client timeout. First: %li, last_client: %li, last_relay: %li.", - client->first_event, client->last_client_event, client->last_relay_event); - redudp_drop_client(client); + redudp_client *client = _arg; + redudp_log_error(client, LOG_DEBUG, "Client timeout. First: %li, last_client: %li, last_relay: %li.", + client->first_event, client->last_client_event, client->last_relay_event); + redudp_drop_client(client); } -static void redudp_first_pkt_from_client(redudp_instance *self, struct sockaddr_in *clientaddr, struct sockaddr_in *destaddr, char *buf, size_t pktlen) +static void redudp_first_pkt_from_client( + redudp_instance *self, + struct sockaddr_storage *clientaddr, + struct sockaddr_storage *destaddr, + char *buf, + size_t pktlen) { - redudp_client *client = calloc(1, sizeof(*client)); + redudp_client *client = calloc(1, sizeof(*client)+self->relay_ss->payload_len); + if (!client) { + log_errno(LOG_WARNING, "calloc"); + return; + } - if (!client) { - log_errno(LOG_WARNING, "calloc"); - return; - } + INIT_LIST_HEAD(&client->list); + INIT_LIST_HEAD(&client->queue); + client->instance = self; + memcpy(&client->clientaddr, clientaddr, sizeof(*clientaddr)); + // TODO: remove client->destaddr + if (destaddr) + memcpy(&client->destaddr, destaddr, sizeof(client->destaddr)); + client->timeoutev = evtimer_new(get_event_base(), redudp_timeout, client); + self->relay_ss->init(client); - INIT_LIST_HEAD(&client->list); - INIT_LIST_HEAD(&client->queue); - client->instance = self; - memcpy(&client->clientaddr, clientaddr, sizeof(*clientaddr)); - if (destaddr) - memcpy(&client->destaddr, destaddr, sizeof(client->destaddr)); - evtimer_set(&client->timeout, redudp_timeout, client); - // XXX: self->relay_ss->init(client); + redsocks_time(&client->first_event); + client->last_client_event = client->first_event; + redudp_bump_timeout(client); - client->sender_fd = -1; // it's postponed until socks-server replies to avoid trivial DoS + list_add(&client->list, &self->clients); - client->relay = red_connect_relay(&client->instance->config.relayaddr, - redudp_relay_connected, redudp_relay_error, client); - if (!client->relay) - goto fail; + redudp_log_error(client, LOG_DEBUG, "got 1st packet from client"); - if (redsocks_time(&client->first_event) == (time_t)-1) - goto fail; - client->last_client_event = client->first_event; - redudp_bump_timeout(client); + if (redudp_enqeue_pkt(client, destaddr, buf, pktlen) == -1) + goto fail; - if (redudp_enqeue_pkt(client, buf, pktlen) == -1) - goto fail; - - list_add(&client->list, &self->clients); - redudp_log_error(client, LOG_INFO, "got 1st packet from client"); - return; + if (self->relay_ss->connect_relay) + self->relay_ss->connect_relay(client); + return; fail: - redudp_drop_client(client); -} - -static void redudp_pkt_from_socks(int fd, short what, void *_arg) -{ - redudp_client *client = _arg; - union { - char buf[0xFFFF]; - socks5_udp_preabmle header; - } pkt; - ssize_t pktlen, fwdlen, outgoing; - struct sockaddr_in udprelayaddr; - - assert(fd == EVENT_FD(&client->udprelay)); - - pktlen = red_recv_udp_pkt(fd, pkt.buf, sizeof(pkt.buf), &udprelayaddr, NULL); - if (pktlen == -1) - return; - - if (memcmp(&udprelayaddr, &client->udprelayaddr, sizeof(udprelayaddr)) != 0) { - char buf[RED_INET_ADDRSTRLEN]; - redudp_log_error(client, LOG_NOTICE, "Got packet from unexpected address %s.", - red_inet_ntop(&udprelayaddr, buf, sizeof(buf))); - return; - } - - if (pkt.header.frag_no != 0) { - // FIXME: does anybody need it? - redudp_log_error(client, LOG_WARNING, "Got fragment #%u. Packet fragmentation is not supported!", - pkt.header.frag_no); - return; - } - - if (pkt.header.addrtype != socks5_addrtype_ipv4) { - redudp_log_error(client, LOG_NOTICE, "Got address type #%u instead of expected #%u (IPv4).", - pkt.header.addrtype, socks5_addrtype_ipv4); - return; - } - - if (pkt.header.ip.port != get_destaddr(client)->sin_port || - pkt.header.ip.addr != get_destaddr(client)->sin_addr.s_addr) - { - char buf[RED_INET_ADDRSTRLEN]; - struct sockaddr_in pktaddr = { - .sin_family = AF_INET, - .sin_addr = { pkt.header.ip.addr }, - .sin_port = pkt.header.ip.port, - }; - redudp_log_error(client, LOG_NOTICE, "Socks5 server relayed packet from unexpected address %s.", - red_inet_ntop(&pktaddr, buf, sizeof(buf))); - return; - } - - redsocks_time(&client->last_relay_event); - redudp_bump_timeout(client); - - if (do_tproxy(client->instance) && client->sender_fd == -1) { - client->sender_fd = bound_udp4_get(&client->destaddr); - if (client->sender_fd == -1) { - redudp_log_error(client, LOG_WARNING, "bound_udp4_get failure"); - return; - } - } - - fwdlen = pktlen - sizeof(pkt.header); - outgoing = sendto(do_tproxy(client->instance) - ? client->sender_fd - : EVENT_FD(&client->instance->listener), - pkt.buf + sizeof(pkt.header), fwdlen, 0, - (struct sockaddr*)&client->clientaddr, sizeof(client->clientaddr)); - if (outgoing != fwdlen) { - redudp_log_error(client, LOG_WARNING, "sendto: I was sending %zd bytes, but only %zd were sent.", - fwdlen, outgoing); - return; - } + redudp_drop_client(client); } static void redudp_pkt_from_client(int fd, short what, void *_arg) { - redudp_instance *self = _arg; - struct sockaddr_in clientaddr, destaddr, *pdestaddr; - char buf[0xFFFF]; // UDP packet can't be larger then that - ssize_t pktlen; - redudp_client *tmp, *client = NULL; - - pdestaddr = do_tproxy(self) ? &destaddr : NULL; - - assert(fd == EVENT_FD(&self->listener)); - pktlen = red_recv_udp_pkt(fd, buf, sizeof(buf), &clientaddr, pdestaddr); - if (pktlen == -1) - return; - - // TODO: this lookup may be SLOOOOOW. - list_for_each_entry(tmp, &self->clients, list) { - // TODO: check destaddr - if (0 == memcmp(&clientaddr, &tmp->clientaddr, sizeof(clientaddr))) { - client = tmp; - break; - } - } - - if (client) { - redsocks_time(&client->last_client_event); - redudp_bump_timeout(client); - if (event_initialized(&client->udprelay)) { - redudp_forward_pkt(client, buf, pktlen); - } - else { - redudp_enqeue_pkt(client, buf, pktlen); - } - } - else { - redudp_first_pkt_from_client(self, &clientaddr, pdestaddr, buf, pktlen); - } + redudp_instance *self = _arg; + struct sockaddr_storage clientaddr, destaddr, *pdestaddr; + ssize_t pktlen; + redudp_client *tmp, *client = NULL; + + pdestaddr = do_tproxy(self) ? &destaddr : NULL; + + assert(fd == event_get_fd(self->listener)); + // destaddr will be filled with true destination if it is available + pktlen = red_recv_udp_pkt(fd, self->shared_buff, MAX_UDP_PACKET_SIZE, &clientaddr, pdestaddr); + if (pktlen == -1) + return; + if (!pdestaddr) + // In case tproxy is not used, use configured destination address instead. + pdestaddr = &self->config.destaddr; + + // TODO: this lookup may be SLOOOOOW. + list_for_each_entry(tmp, &self->clients, list) { + if (0 == evutil_sockaddr_cmp((struct sockaddr *)&clientaddr, + (struct sockaddr *)&tmp->clientaddr, + 1)) { + + client = tmp; + break; + } + } + + if (client) { + redsocks_time(&client->last_client_event); + redudp_bump_timeout(client); + + if (self->relay_ss->ready_to_fwd(client)) { + self->relay_ss->forward_pkt(client, (struct sockaddr *)pdestaddr, self->shared_buff, pktlen); + } + else { + redudp_enqeue_pkt(client, pdestaddr, self->shared_buff, pktlen); + } + } + else { + redudp_first_pkt_from_client(self, &clientaddr, pdestaddr, self->shared_buff, pktlen); + } } /*********************************************************************** @@ -673,146 +489,255 @@ static void redudp_pkt_from_client(int fd, short what, void *_arg) */ static parser_entry redudp_entries[] = { - { .key = "local_ip", .type = pt_in_addr }, - { .key = "local_port", .type = pt_uint16 }, - { .key = "ip", .type = pt_in_addr }, - { .key = "port", .type = pt_uint16 }, - { .key = "login", .type = pt_pchar }, - { .key = "password", .type = pt_pchar }, - { .key = "dest_ip", .type = pt_in_addr }, - { .key = "dest_port", .type = pt_uint16 }, - { .key = "udp_timeout", .type = pt_uint16 }, - { .key = "udp_timeout_stream", .type = pt_uint16 }, - { } + { .key = "bind", .type = pt_pchar }, + { .key = "relay", .type = pt_pchar }, + { .key = "dest", .type = pt_pchar }, + { .key = "type", .type = pt_pchar }, + { .key = "login", .type = pt_pchar }, + { .key = "password", .type = pt_pchar }, + { .key = "udp_timeout", .type = pt_uint16 }, + { .key = "udp_timeout_stream", .type = pt_uint16 }, + { .key = "max_pktqueue", .type = pt_uint16 }, + { } }; static list_head instances = LIST_HEAD_INIT(instances); static int redudp_onenter(parser_section *section) { - redudp_instance *instance = calloc(1, sizeof(*instance)); - if (!instance) { - parser_error(section->context, "Not enough memory"); - return -1; - } - - INIT_LIST_HEAD(&instance->list); - INIT_LIST_HEAD(&instance->clients); - instance->config.bindaddr.sin_family = AF_INET; - instance->config.bindaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - instance->config.relayaddr.sin_family = AF_INET; - instance->config.relayaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - instance->config.destaddr.sin_family = AF_INET; - instance->config.max_pktqueue = 5; - instance->config.udp_timeout = 30; - instance->config.udp_timeout_stream = 180; - - for (parser_entry *entry = §ion->entries[0]; entry->key; entry++) - entry->addr = - (strcmp(entry->key, "local_ip") == 0) ? (void*)&instance->config.bindaddr.sin_addr : - (strcmp(entry->key, "local_port") == 0) ? (void*)&instance->config.bindaddr.sin_port : - (strcmp(entry->key, "ip") == 0) ? (void*)&instance->config.relayaddr.sin_addr : - (strcmp(entry->key, "port") == 0) ? (void*)&instance->config.relayaddr.sin_port : - (strcmp(entry->key, "login") == 0) ? (void*)&instance->config.login : - (strcmp(entry->key, "password") == 0) ? (void*)&instance->config.password : - (strcmp(entry->key, "dest_ip") == 0) ? (void*)&instance->config.destaddr.sin_addr : - (strcmp(entry->key, "dest_port") == 0) ? (void*)&instance->config.destaddr.sin_port : - (strcmp(entry->key, "max_pktqueue") == 0) ? (void*)&instance->config.max_pktqueue : - (strcmp(entry->key, "udp_timeout") == 0) ? (void*)&instance->config.udp_timeout: - (strcmp(entry->key, "udp_timeout_stream") == 0) ? (void*)&instance->config.udp_timeout_stream : - NULL; - section->data = instance; - return 0; + // FIXME: find proper way to calulate instance_payload_len + int instance_payload_len = 0; + udprelay_subsys **ss; + FOREACH(ss, relay_subsystems) + if (instance_payload_len < (*ss)->instance_payload_len) + instance_payload_len = (*ss)->instance_payload_len; + + redudp_instance *instance = calloc(1, sizeof(*instance) + instance_payload_len); + if (!instance) { + parser_error(section->context, "Not enough memory"); + return -1; + } + + INIT_LIST_HEAD(&instance->list); + INIT_LIST_HEAD(&instance->clients); + struct sockaddr_in * addr = (struct sockaddr_in *)&instance->config.bindaddr; + addr->sin_family = AF_INET; + addr->sin_addr.s_addr = htonl(INADDR_LOOPBACK); + instance->config.max_pktqueue = DEFAULT_MAX_PKTQUEUE; + instance->config.udp_timeout = DEFAULT_UDP_TIMEOUT; + instance->config.udp_timeout_stream = 180; + + for (parser_entry *entry = §ion->entries[0]; entry->key; entry++) + entry->addr = + (strcmp(entry->key, "bind") == 0) ? (void*)&instance->config.bind : + (strcmp(entry->key, "relay") == 0) ? (void*)&instance->config.relay : + (strcmp(entry->key, "dest") == 0) ? (void*)&instance->config.dest : + (strcmp(entry->key, "type") == 0) ? (void*)&instance->config.type : + (strcmp(entry->key, "login") == 0) ? (void*)&instance->config.login : + (strcmp(entry->key, "password") == 0) ? (void*)&instance->config.password : + (strcmp(entry->key, "max_pktqueue") == 0) ? (void*)&instance->config.max_pktqueue : + (strcmp(entry->key, "udp_timeout") == 0) ? (void*)&instance->config.udp_timeout: + (strcmp(entry->key, "udp_timeout_stream") == 0) ? (void*)&instance->config.udp_timeout_stream : + NULL; + section->data = instance; + return 0; } static int redudp_onexit(parser_section *section) { - redudp_instance *instance = section->data; - - section->data = NULL; - for (parser_entry *entry = §ion->entries[0]; entry->key; entry++) - entry->addr = NULL; - - instance->config.bindaddr.sin_port = htons(instance->config.bindaddr.sin_port); - instance->config.relayaddr.sin_port = htons(instance->config.relayaddr.sin_port); - instance->config.destaddr.sin_port = htons(instance->config.destaddr.sin_port); - - if (instance->config.udp_timeout_stream < instance->config.udp_timeout) { - parser_error(section->context, "udp_timeout_stream should be not less then udp_timeout"); - return -1; - } - - list_add(&instance->list, &instances); - - return 0; + redudp_instance *instance = section->data; + char * err = NULL; + + section->data = NULL; + for (parser_entry *entry = §ion->entries[0]; entry->key; entry++) + entry->addr = NULL; + + // Parse and update bind address and relay address + if (instance->config.bind) { + struct sockaddr * addr = (struct sockaddr *)&instance->config.bindaddr; + int addr_size = sizeof(instance->config.bindaddr); + if (evutil_parse_sockaddr_port(instance->config.bind, addr, &addr_size)) + err = "invalid bind address"; + } + if (!err && instance->config.relay) { + struct sockaddr * addr = (struct sockaddr *)&instance->config.relayaddr; + int addr_size = sizeof(instance->config.relayaddr); + if (evutil_parse_sockaddr_port(instance->config.relay, addr, &addr_size)) { + char * pos = strchr(instance->config.relay, ':'); + char * host = NULL; + if (pos != NULL) + host = strndup(instance->config.relay, pos - instance->config.relay); + else + host = instance->config.relay; + int result = resolve_hostname(host, AF_INET, addr); + if (result != 0) { + result = resolve_hostname(host, AF_INET6, addr); + } + if (result != 0) { + err = "invalid relay address"; + } + if (!err && pos != NULL) { + if (addr->sa_family == AF_INET) + ((struct sockaddr_in*)addr)->sin_port = htons(atoi(pos+1)); + else + ((struct sockaddr_in6*)addr)->sin6_port = htons(atoi(pos+1)); + } + if (host != instance->config.relay) + free(host); + } + } + else if (!instance->config.relay) + err = "missing relay address"; + if (!err && instance->config.dest) { + struct sockaddr * addr = (struct sockaddr *)&instance->config.destaddr; + int addr_size = sizeof(instance->config.destaddr); + if (evutil_parse_sockaddr_port(instance->config.dest, addr, &addr_size)) + err = "invalid dest address"; + } + + if (!err && instance->config.type) { + udprelay_subsys **ss; + FOREACH(ss, relay_subsystems) { + if (!strcmp((*ss)->name, instance->config.type)) { + instance->relay_ss = *ss; + list_add(&instance->list, &instances); + break; + } + } + if (!instance->relay_ss) + err = "invalid `type` for redudp"; + } + else if (!err) { + err = "no `type` for redudp"; + } + + if (instance->config.max_pktqueue == 0) { + parser_error(section->context, "max_pktqueue must be greater than 0."); + return -1; + } + if (instance->config.udp_timeout == 0) { + parser_error(section->context, "udp_timeout must be greater than 0."); + return -1; + } + if (instance->config.udp_timeout_stream < instance->config.udp_timeout) { + parser_error(section->context, "udp_timeout_stream should be not less than udp_timeout"); + return -1; + } + + if (err) + parser_error(section->context, "%s", err); + + return err?-1:0; } static int redudp_init_instance(redudp_instance *instance) { - /* FIXME: redudp_fini_instance is called in case of failure, this - * function will remove instance from instances list - result - * looks ugly. - */ - int error; - int fd = -1; - - fd = socket(AF_INET, SOCK_DGRAM, 0); - if (fd == -1) { - log_errno(LOG_ERR, "socket"); - goto fail; - } - - if (do_tproxy(instance)) { - int on = 1; - char buf[RED_INET_ADDRSTRLEN]; - // iptables TPROXY target does not send packets to non-transparent sockets - if (0 != redudp_transparent(fd)) - goto fail; - - error = setsockopt(fd, SOL_IP, IP_RECVORIGDSTADDR, &on, sizeof(on)); - if (error) { - log_errno(LOG_ERR, "setsockopt(listener, SOL_IP, IP_RECVORIGDSTADDR)"); - goto fail; - } - - log_error(LOG_DEBUG, "redudp @ %s: TPROXY", red_inet_ntop(&instance->config.bindaddr, buf, sizeof(buf))); - } - else { - char buf1[RED_INET_ADDRSTRLEN], buf2[RED_INET_ADDRSTRLEN]; - log_error(LOG_DEBUG, "redudp @ %s: destaddr=%s", - red_inet_ntop(&instance->config.bindaddr, buf1, sizeof(buf1)), - red_inet_ntop(&instance->config.destaddr, buf2, sizeof(buf2))); - } - - error = bind(fd, (struct sockaddr*)&instance->config.bindaddr, sizeof(instance->config.bindaddr)); - if (error) { - log_errno(LOG_ERR, "bind"); - goto fail; - } - - error = fcntl_nonblock(fd); - if (error) { - log_errno(LOG_ERR, "fcntl"); - goto fail; - } - - event_set(&instance->listener, fd, EV_READ | EV_PERSIST, redudp_pkt_from_client, instance); - error = event_add(&instance->listener, NULL); - if (error) { - log_errno(LOG_ERR, "event_add"); - goto fail; - } - - return 0; + /* FIXME: redudp_fini_instance is called in case of failure, this + * function will remove instance from instances list - result + * looks ugly. + */ + int error; + int fd = -1; + int bindaddr_len = 0; + char buf1[RED_INET_ADDRSTRLEN], + buf2[RED_INET_ADDRSTRLEN], + buf3[RED_INET_ADDRSTRLEN]; + + instance->shared_buff = &shared_buff[0]; + if (instance->relay_ss->instance_init + && instance->relay_ss->instance_init(instance)) { + log_errno(LOG_ERR, "Failed to init UDP relay subsystem."); + goto fail; + } + + fd = socket(instance->config.bindaddr.ss_family, SOCK_DGRAM, IPPROTO_UDP); + if (fd == -1) { + log_errno(LOG_ERR, "socket"); + goto fail; + } + + if (do_tproxy(instance)) { + int on = 1; + // iptables TPROXY target does not send packets to non-transparent sockets + if (0 != make_socket_transparent(fd)) + goto fail; + +#ifdef SOL_IPV6 + if (instance->config.bindaddr.ss_family == AF_INET) { +#endif + error = setsockopt(fd, SOL_IP, IP_RECVORIGDSTADDR, &on, sizeof(on)); + if (error) { + log_errno(LOG_ERR, "setsockopt(listener, SOL_IP, IP_RECVORIGDSTADDR)"); + goto fail; + } +#if defined(__OpenBSD__) || defined(__NetBSD__) + setsockopt(fd, IPPROTO_IP, IP_RECVDSTADDR, &on, sizeof(on)); + setsockopt(fd, IPPROTO_IP, IP_RECVDSTPORT, &on, sizeof(on)); +#endif +#ifdef SOL_IPV6 + } + else { + error = setsockopt(fd, SOL_IPV6, IPV6_RECVORIGDSTADDR, &on, sizeof(on)); + if (error) { + log_errno(LOG_ERR, "setsockopt(listener, SOL_IPV6, IPV6_RECVORIGDSTADDR)"); + goto fail; + } +#if defined(__OpenBSD__) || defined(__NetBSD__) + setsockopt(fd, IPPROTO_IP, IPV6_RECVPKTINFO, &on, sizeof(on)); + setsockopt(fd, IPPROTO_IP, IPV6_RECVDSTPORT, &on, sizeof(on)); +#endif + } +#endif + log_error(LOG_INFO, "redudp @ %s: TPROXY", red_inet_ntop(&instance->config.bindaddr, buf1, sizeof(buf1))); + } + else { + log_error(LOG_INFO, "redudp @ %s: relay=%s destaddr=%s", + red_inet_ntop(&instance->config.bindaddr, buf1, sizeof(buf1)), + red_inet_ntop(&instance->config.relayaddr, buf2, sizeof(buf2)), + red_inet_ntop(&instance->config.destaddr, buf3, sizeof(buf3))); + } + + if (apply_reuseport(fd)) + log_error(LOG_WARNING, "Continue without SO_REUSEPORT enabled"); + +#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) + bindaddr_len = instance->config.bindaddr.ss_len > 0 ? instance->config.bindaddr.ss_len : sizeof(instance->config.bindaddr); +#else + bindaddr_len = sizeof(instance->config.bindaddr); +#endif + error = bind(fd, (struct sockaddr*)&instance->config.bindaddr, bindaddr_len); + if (error) { + log_errno(LOG_ERR, "bind"); + goto fail; + } + + error = evutil_make_socket_nonblocking(fd); + if (error) { + log_errno(LOG_ERR, "set nonblocking"); + goto fail; + } + + instance->listener = event_new(get_event_base(), fd, EV_READ | EV_PERSIST, redudp_pkt_from_client, instance); + if (!instance->listener) { + log_errno(LOG_ERR, "event_new"); + goto fail; + } + error = event_add(instance->listener, NULL); + if (error) { + log_errno(LOG_ERR, "event_add"); + goto fail; + } + + return 0; fail: - redudp_fini_instance(instance); + redudp_fini_instance(instance); - if (fd != -1) { - redsocks_close(fd); - } + if (fd != -1) { + close(fd); + } - return -1; + return -1; } /* Drops instance completely, freeing its memory and removing from @@ -820,72 +745,94 @@ static int redudp_init_instance(redudp_instance *instance) */ static void redudp_fini_instance(redudp_instance *instance) { - if (!list_empty(&instance->clients)) { - redudp_client *tmp, *client = NULL; + if (!list_empty(&instance->clients)) { + redudp_client *tmp, *client = NULL; - log_error(LOG_WARNING, "There are connected clients during shutdown! Disconnecting them."); - list_for_each_entry_safe(client, tmp, &instance->clients, list) { - redudp_drop_client(client); - } - } + log_error(LOG_WARNING, "There are connected clients during shutdown! Disconnecting them."); + list_for_each_entry_safe(client, tmp, &instance->clients, list) { + redudp_drop_client(client); + } + } - if (event_initialized(&instance->listener)) { - if (event_del(&instance->listener) != 0) - log_errno(LOG_WARNING, "event_del"); - redsocks_close(EVENT_FD(&instance->listener)); - memset(&instance->listener, 0, sizeof(instance->listener)); - } + if (instance->listener) { + if (event_del(instance->listener) != 0) + log_errno(LOG_WARNING, "event_del"); + close(event_get_fd(instance->listener)); + memset(&instance->listener, 0, sizeof(instance->listener)); + } - list_del(&instance->list); + if (instance->relay_ss->instance_fini) + instance->relay_ss->instance_fini(instance); - free(instance->config.login); - free(instance->config.password); + list_del(&instance->list); + free(instance->config.type); + free(instance->config.login); + free(instance->config.password); - memset(instance, 0, sizeof(*instance)); - free(instance); + memset(instance, 0, sizeof(*instance)); + free(instance); +} + +static struct event * audit_event = NULL; + +static void redudp_audit(int sig, short what, void *_arg) +{ + twalk(root_bound_udp, bound_udp_action); } static int redudp_init() { - redudp_instance *tmp, *instance = NULL; + redudp_instance *tmp, *instance = NULL; + struct timeval audit_time; + struct event_base * base = get_event_base(); - // TODO: init debug_dumper + list_for_each_entry_safe(instance, tmp, &instances, list) { + if (redudp_init_instance(instance) != 0) + goto fail; + } - list_for_each_entry_safe(instance, tmp, &instances, list) { - if (redudp_init_instance(instance) != 0) - goto fail; - } + /* Start audit */ + audit_time.tv_sec = REDUDP_AUDIT_INTERVAL; + audit_time.tv_usec = 0; + audit_event = event_new(base, -1, EV_TIMEOUT|EV_PERSIST, redudp_audit, NULL); + evtimer_add(audit_event, &audit_time); - return 0; + return 0; fail: - redudp_fini(); - return -1; + redudp_fini(); + return -1; } static int redudp_fini() { - redudp_instance *tmp, *instance = NULL; + redudp_instance *tmp, *instance = NULL; - list_for_each_entry_safe(instance, tmp, &instances, list) - redudp_fini_instance(instance); + /* stop audit */ + if (audit_event) { + evtimer_del(audit_event); + event_free(audit_event); + audit_event = NULL; + } + list_for_each_entry_safe(instance, tmp, &instances, list) + redudp_fini_instance(instance); - return 0; + return 0; } static parser_section redudp_conf_section = { - .name = "redudp", - .entries = redudp_entries, - .onenter = redudp_onenter, - .onexit = redudp_onexit + .name = "redudp", + .entries = redudp_entries, + .onenter = redudp_onenter, + .onexit = redudp_onexit }; app_subsys redudp_subsys = { - .init = redudp_init, - .fini = redudp_fini, - .conf_section = &redudp_conf_section, + .init = redudp_init, + .fini = redudp_fini, + .conf_section = &redudp_conf_section, }; /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ diff --git a/redudp.h b/redudp.h index 3f1d9d10..70f55e60 100644 --- a/redudp.h +++ b/redudp.h @@ -1,11 +1,39 @@ #ifndef REDUDP_H #define REDUDP_H +#include +#include "list.h" + +#define MAX_UDP_PACKET_SIZE 0xFFFF + +struct redudp_client_t; +struct redudp_instance_t; + +typedef struct udprelay_subsys_t { + char *name; + size_t payload_len; // size of relay-specific data in client section + size_t instance_payload_len; // size of relay-specify data in instance section + void (*init)(struct redudp_client_t *client); + void (*fini)(struct redudp_client_t *client); + int (*instance_init)(struct redudp_instance_t *instance); + void (*instance_fini)(struct redudp_instance_t *instance); + // connect_relay (if any) is called instead of redudp_connect_relay after client connection acceptance + void (*connect_relay)(struct redudp_client_t *client); + //void (*relay_connected)(struct redudp_client_t *client); + void (*forward_pkt)(struct redudp_client_t *client, struct sockaddr * destaddr, void * data, size_t len); + int (*ready_to_fwd)(struct redudp_client_t *client); +} udprelay_subsys; + + typedef struct redudp_config_t { - struct sockaddr_in bindaddr; - struct sockaddr_in relayaddr; + struct sockaddr_storage bindaddr; + struct sockaddr_storage relayaddr; // TODO: outgoingaddr; - struct sockaddr_in destaddr; + struct sockaddr_storage destaddr; + char *bind; + char *relay; + char *dest; + char *type; char *login; char *password; uint16_t max_pktqueue; @@ -16,34 +44,44 @@ typedef struct redudp_config_t { typedef struct redudp_instance_t { list_head list; redudp_config config; - struct event listener; + struct event * listener; list_head clients; + udprelay_subsys*relay_ss; + void * shared_buff; // pointer to 64K buffer shared by clients for receiving/processing udp packets } redudp_instance; typedef struct redudp_client_t { list_head list; - redudp_instance *instance; - struct sockaddr_in clientaddr; - struct sockaddr_in destaddr; - int sender_fd; // shared between several clients socket (bound to `destaddr`) - struct event timeout; - struct bufferevent *relay; - struct event udprelay; - struct sockaddr_in udprelayaddr; + redudp_instance * instance; + struct sockaddr_storage clientaddr; + struct sockaddr_storage destaddr; + struct event * timeoutev; int state; // it's used by bottom layer time_t first_event; time_t last_client_event; time_t last_relay_event; - unsigned int queue_len; + uint16_t queue_len; list_head queue; } redudp_client; typedef struct enqueued_packet_t { list_head list; + struct sockaddr_storage destaddr; size_t len; char data[1]; } enqueued_packet; +struct sockaddr_storage* get_destaddr(redudp_client *client); +void redudp_drop_client(redudp_client *client); +void redudp_flush_queue(redudp_client *client); +void redudp_fwd_pkt_to_sender(redudp_client *client, void *buf, size_t len, struct sockaddr_storage * srcaddr); +void redudp_bump_timeout(redudp_client *client); + +#define redudp_log_error(client, prio, msg...) \ + redsocks_log_write_plain(__FILE__, __LINE__, __func__, 0, &(client)->clientaddr, get_destaddr(client), prio, ## msg) +#define redudp_log_errno(client, prio, msg...) \ + redsocks_log_write_plain(__FILE__, __LINE__, __func__, 1, &(client)->clientaddr, get_destaddr(client), prio, ## msg) + /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ #endif /* REDUDP_H */ diff --git a/shadowsocks-udp.c b/shadowsocks-udp.c new file mode 100644 index 00000000..82bb5885 --- /dev/null +++ b/shadowsocks-udp.c @@ -0,0 +1,328 @@ +/* redsocks2 - transparent TCP-to-proxy redirector + * Copyright (C) 2013-2017 Zhuofei Wang + * + * This code is based on redsocks project developed by Leonid Evdokimov. + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#include +#include +#include +#include +#include +#include "utils.h" +#include "log.h" +#include "redsocks.h" +#include "main.h" +#include "redudp.h" +#include "encrypt.h" +#include "shadowsocks.h" + +typedef struct ss_client_t { + struct event * udprelay; +} ss_client; + +typedef struct ss_instance_t { + int method; + enc_info info; + struct enc_ctx e_ctx; + struct enc_ctx d_ctx; + void * buff; +} ss_instance; + + +static int ss_is_valid_cred(const char *method, const char *password) +{ + if (!method || !password) + return 0; + if (strlen(method) > 255) { + log_error(LOG_WARNING, "Shadowsocks encryption method can't be more than 255 chars."); + return 0; + } + if (strlen(password) > 255) { + log_error(LOG_WARNING, "Shadowsocks encryption password can't be more than 255 chars."); + return 0; + } + return 1; +} + +static void ss_client_init(redudp_client *client) +{ +} + +static void ss_client_fini(redudp_client *client) +{ + ss_client *ssclient = (void*)(client + 1); + if (ssclient->udprelay) { + int fd = event_get_fd(ssclient->udprelay); + if (event_del(ssclient->udprelay) == -1) + redudp_log_errno(client, LOG_ERR, "event_del"); + event_free(ssclient->udprelay); + close(fd); + } +} + +static void ss_forward_pkt(redudp_client *client, struct sockaddr* destaddr, void *data, size_t pktlen) +{ + ss_client *ssclient = (void*)(client + 1); + ss_instance * ss = (ss_instance *)(client->instance+1); + struct sockaddr_storage * relayaddr = &client->instance->config.relayaddr; + struct msghdr msg; + struct iovec io[1]; + ssize_t outgoing; + int rc; + ss_header header; + size_t len = 0; + size_t header_len = 0; + size_t fwdlen = 0; + void * buff = client->instance->shared_buff; + + /* build and send header */ + if (client->destaddr.ss_family == AF_INET) { + struct sockaddr_in * addr = (struct sockaddr_in *)&client->destaddr; + header.v4.addr_type = ss_addrtype_ipv4; + header.v4.addr = addr->sin_addr.s_addr; + header.v4.port = addr->sin_port; + header_len = sizeof(ss_header_ipv4); + } + else if (client->destaddr.ss_family == AF_INET6) { + struct sockaddr_in6 * addr = (struct sockaddr_in6 *)&client->destaddr; + header.v6.addr_type = ss_addrtype_ipv6; + header.v6.addr = addr->sin6_addr; + header.v6.port = addr->sin6_port; + header_len = sizeof(ss_header_ipv6); + } + else { + redudp_log_error(client, LOG_ERR, "Unsupported address family: %d", client->destaddr.ss_family); + return ; + } + + if (enc_ctx_init(&ss->info, &ss->e_ctx, 1)) { + redudp_log_error(client, LOG_ERR, "Shadowsocks UDP failed to initialize encryption context."); + return; + } + rc = ss_encrypt(&ss->e_ctx, (char *)&header, header_len, buff, &len); + if (rc) + { + if (len + pktlen < MAX_UDP_PACKET_SIZE) + rc = ss_encrypt(&ss->e_ctx, (char *)data, pktlen, buff+len, &fwdlen); + else + rc = 0; + } + enc_ctx_free(&ss->e_ctx); + if (!rc) + { + redudp_log_error(client, LOG_DEBUG, "Can't encrypt packet, dropping it"); + return; + } + fwdlen += len; + + memset(&msg, 0, sizeof(msg)); + msg.msg_name = relayaddr; + msg.msg_namelen = sizeof(*relayaddr); + msg.msg_iov = io; + msg.msg_iovlen = SIZEOF_ARRAY(io); + + io[0].iov_base = buff; + io[0].iov_len = fwdlen; + + outgoing = sendmsg(event_get_fd(ssclient->udprelay), &msg, 0); + if (outgoing == -1) { + redudp_log_errno(client, LOG_DEBUG, "sendmsg: Can't forward packet, dropping it"); + return; + } + else if (outgoing != fwdlen) { + redudp_log_error(client, LOG_DEBUG, "sendmsg: I was sending %zd bytes, but only %zd were sent.", fwdlen, outgoing); + return; + } +} + +static void ss_pkt_from_server(int fd, short what, void *_arg) +{ + redudp_client *client = _arg; + ss_client *ssclient = (void*)(client + 1); + ss_instance * ss = (ss_instance *)(client->instance+1); + ss_header * header; + ssize_t pktlen; + size_t fwdlen; + struct sockaddr_storage udprelayaddr; + int rc; + void * buff = client->instance->shared_buff; + void * buff2 = ss->buff; + + assert(fd == event_get_fd(ssclient->udprelay)); + + pktlen = red_recv_udp_pkt(fd, buff, MAX_UDP_PACKET_SIZE, &udprelayaddr, NULL); + if (pktlen == -1) + return; + + if (enc_ctx_init(&ss->info, &ss->d_ctx, 0)) { + redudp_log_error(client, LOG_ERR, "Shadowsocks UDP failed to initialize decryption context."); + return; + } + rc = ss_decrypt(&ss->d_ctx, buff, pktlen, buff2, &fwdlen); + enc_ctx_free(&ss->d_ctx); + if (!rc) { + redudp_log_error(client, LOG_DEBUG, "Can't decrypt packet, dropping it"); + return; + } + header = (ss_header*)buff2; + // We do not verify src address, but at least, we need to ensure address type is correct. + if (header->addr_type == ss_addrtype_ipv4) { + struct sockaddr_in pktaddr = { + .sin_family = AF_INET, + .sin_addr = { header->v4.addr }, + .sin_port = header->v4.port, + }; + + if (fwdlen < sizeof(header->v4)) { + redudp_log_error(client, LOG_DEBUG, "Packet too short."); + return; + } + fwdlen -= sizeof(header->v4); + redudp_fwd_pkt_to_sender( + client, + buff2 + sizeof(header->v4), + fwdlen, + (struct sockaddr_storage*)&pktaddr); + } + else if (header->addr_type == ss_addrtype_ipv6) { + struct sockaddr_in6 pktaddr = { + .sin6_family = AF_INET6, + .sin6_port = header->v6.port, + }; + memcpy(&pktaddr.sin6_addr, &header->v6.addr, sizeof(header->v6.addr)); + + if (fwdlen < sizeof(header->v6)) { + redudp_log_error(client, LOG_DEBUG, "Packet too short."); + return; + } + fwdlen -= sizeof(header->v6); + redudp_fwd_pkt_to_sender( + client, + buff2 + sizeof(header->v6), + fwdlen, + (struct sockaddr_storage*)&pktaddr); + } + else { + redudp_log_error(client, LOG_DEBUG, "Got address type #%u instead of expected #%u (IPv4/IPv6).", + header->addr_type, ss_addrtype_ipv4); + return; + } +} + +static int ss_ready_to_fwd(struct redudp_client_t *client) +{ + return 1; +} + +static void ss_connect_relay(redudp_client *client) +{ + ss_client *ssclient = (void*)(client + 1); + struct sockaddr_storage * addr = &client->instance->config.relayaddr; + int fd = -1; + int error; + + fd = socket(addr->ss_family, SOCK_DGRAM, IPPROTO_UDP); + if (fd == -1) { + redudp_log_errno(client, LOG_ERR, "socket"); + goto fail; + } + + error = evutil_make_socket_nonblocking(fd); + if (error) { + redudp_log_errno(client, LOG_ERR, "evutil_make_socket_nonblocking"); + goto fail; + } + + error = connect(fd, (struct sockaddr*)addr, sizeof(*addr)); + if (error) { + redudp_log_errno(client, LOG_NOTICE, "connect"); + goto fail; + } + + ssclient->udprelay = event_new(get_event_base(), fd, EV_READ | EV_PERSIST, ss_pkt_from_server, client); + if (!ssclient->udprelay) { + redudp_log_errno(client, LOG_ERR, "event_new"); + goto fail; + } + error = event_add(ssclient->udprelay, NULL); + if (error) { + redudp_log_errno(client, LOG_ERR, "event_add"); + goto fail; + } + + redudp_flush_queue(client); + return; + +fail: + if (fd != -1) + close(fd); + redudp_drop_client(client); +} + +static int ss_instance_init(struct redudp_instance_t *instance) +{ + ss_instance * ss = (ss_instance *)(instance+1); + const redudp_config *config = &instance->config; + char buf1[RED_INET_ADDRSTRLEN]; + + int valid_cred = ss_is_valid_cred(config->login, config->password); + if (!valid_cred + || (ss->method = enc_init(&ss->info, config->password, config->login), ss->method == -1)) + { + log_error(LOG_ERR, "Invalided encrytion method or password."); + return -1; + } + else + { + log_error(LOG_INFO, "%s @ %s: encryption method: %s", + instance->relay_ss->name, + red_inet_ntop(&instance->config.bindaddr, buf1, sizeof(buf1)), + config->login); + } + // An additional buffer is allocated for each instance for encryption/decrption. + ss->buff = malloc(MAX_UDP_PACKET_SIZE); + if (!ss->buff) { + log_error(LOG_ERR, "Out of memory."); + return -1; + } + return 0; +} + +static void ss_instance_fini(struct redudp_instance_t *instance) +{ + ss_instance * ss = (ss_instance *)(instance+1); + if (ss->buff) { + free(ss->buff); + ss->buff = NULL; + } +} + +udprelay_subsys shadowsocks_udp_subsys = +{ + .name = "shadowsocks", + .payload_len = sizeof(ss_client), + .instance_payload_len = sizeof(ss_instance), + .init = ss_client_init, + .fini = ss_client_fini, + .instance_init = ss_instance_init, + .instance_fini = ss_instance_fini, + .connect_relay = ss_connect_relay, + .forward_pkt = ss_forward_pkt, + .ready_to_fwd = ss_ready_to_fwd, +}; + + +/* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ +/* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ diff --git a/shadowsocks.c b/shadowsocks.c new file mode 100644 index 00000000..5cd97ae8 --- /dev/null +++ b/shadowsocks.c @@ -0,0 +1,435 @@ +/* redsocks2 - transparent TCP-to-proxy redirector + * Copyright (C) 2013-2017 Zhuofei Wang + * + * This code is based on redsocks project developed by Leonid Evdokimov. + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#include +#include +#include +#include +#include +#include +#include "utils.h" +#include "log.h" +#include "redsocks.h" +#include "encrypt.h" +#include "shadowsocks.h" + +typedef enum ss_state_t { + ss_new, + ss_connected, + ss_MAX, +} ss_state; + +typedef struct ss_client_t { + struct enc_ctx e_ctx; + struct enc_ctx d_ctx; + short e_ctx_init; + short d_ctx_init; +} ss_client; + +typedef struct ss_instance_t { + int method; + enc_info info; +} ss_instance; + +void redsocks_event_error(struct bufferevent *buffev, short what, void *_arg); + +int ss_is_valid_cred(const char *method, const char *password) +{ + if (!method || !password) + return 0; + if (strlen(method) > 255) { + log_error(LOG_WARNING, "Shadowsocks encryption method can't be more than 255 chars."); + return 0; + } + if (strlen(password) > 255) { + log_error(LOG_WARNING, "Shadowsocks encryption password can't be more than 255 chars."); + return 0; + } + return 1; +} + +static void ss_client_init(redsocks_client *client) +{ + client->state = ss_new; +} + +static void ss_client_fini(redsocks_client *client) +{ + ss_client *sclient = (void*)(client + 1); + + if (sclient->e_ctx_init) { + enc_ctx_free(&sclient->e_ctx); + sclient->e_ctx_init = 0; + } + if (sclient->d_ctx_init) { + enc_ctx_free(&sclient->d_ctx); + sclient->d_ctx_init = 0; + } +} + +static void encrypt_mem(redsocks_client * client, + char * data, size_t len, + struct evbuffer * buf_out, int decrypt) +{ + ss_client *sclient = (void*)(client + 1); + struct evbuffer_iovec vec; + size_t required; + int rc; + + if (!len || !data) + return; + + if (decrypt) + required = ss_calc_buffer_size(&sclient->d_ctx, len); + else + required = ss_calc_buffer_size(&sclient->e_ctx, len); + if (required && evbuffer_reserve_space(buf_out, required, &vec, 1) == 1) + { + if (decrypt) + rc = ss_decrypt(&sclient->d_ctx, data, len, vec.iov_base, &vec.iov_len); + else + rc = ss_encrypt(&sclient->e_ctx, data, len, vec.iov_base, &vec.iov_len); + if (!rc) + vec.iov_len = 0; + evbuffer_commit_space(buf_out, &vec, 1); + } +} + + +static void encrypt_buffer(redsocks_client *client, + struct bufferevent * from, + struct bufferevent * to) +{ + // To reduce memory copy, just encrypt one block a time + struct evbuffer * buf_in = bufferevent_get_input(from); + size_t input_size = evbuffer_get_contiguous_space(buf_in); + char * input; + + if (!input_size) + return; + + input = (char *)evbuffer_pullup(buf_in, input_size); + encrypt_mem(client, input, input_size, bufferevent_get_output(to), 0); + evbuffer_drain(buf_in, input_size); +} + +static void decrypt_buffer(redsocks_client * client, + struct bufferevent * from, + struct bufferevent * to) +{ + // To reduce memory copy, just decrypt one block a time + struct evbuffer * buf_in = bufferevent_get_input(from); + size_t input_size = evbuffer_get_contiguous_space(buf_in); + char * input; + + if (!input_size) + return; + + input = (char *)evbuffer_pullup(buf_in, input_size); + encrypt_mem(client, input, input_size, bufferevent_get_output(to), 1); + evbuffer_drain(buf_in, input_size); +} + + +static void ss_client_writecb(struct bufferevent *buffev, void *_arg) +{ + redsocks_client *client = _arg; + struct bufferevent * from = client->relay; + struct bufferevent * to = buffev; + size_t input_size = evbuffer_get_contiguous_space(bufferevent_get_input(from)); + size_t output_size = evbuffer_get_length(bufferevent_get_output(to)); + + assert(buffev == client->client); + redsocks_touch_client(client); + + if (process_shutdown_on_write_(client, from, to)) + return; + + if (client->state == ss_connected) + { + /* encrypt and forward data received from client side */ + if (output_size < get_write_hwm(to)) + { + if (input_size) + decrypt_buffer(client, from, to); + if (!(client->relay_evshut & EV_READ) && bufferevent_enable(from, EV_READ) == -1) + redsocks_log_errno(client, LOG_ERR, "bufferevent_enable"); + } + } + else + { + redsocks_drop_client(client); + } +} + +static void ss_client_readcb(struct bufferevent *buffev, void *_arg) +{ + redsocks_client *client = _arg; + struct bufferevent * from = buffev; + struct bufferevent * to = client->relay; + size_t output_size = evbuffer_get_length(bufferevent_get_output(to)); + + assert(buffev == client->client); + redsocks_touch_client(client); + + if (client->state == ss_connected) + { + /* encrypt and forward data to the other side */ + if (output_size < get_write_hwm(to)) + { + encrypt_buffer(client, from, to); + if (bufferevent_enable(from, EV_READ) == -1) + redsocks_log_errno(client, LOG_ERR, "bufferevent_enable"); + } + else + { + if (bufferevent_disable(from, EV_READ) == -1) + redsocks_log_errno(client, LOG_ERR, "bufferevent_disable"); + } + } + else + { + redsocks_drop_client(client); + } +} + + +static void ss_relay_writecb(struct bufferevent *buffev, void *_arg) +{ + redsocks_client *client = _arg; + struct bufferevent * from = client->client; + struct bufferevent * to = buffev; + size_t input_size = evbuffer_get_contiguous_space(bufferevent_get_input(from)); + size_t output_size = evbuffer_get_length(bufferevent_get_output(to)); + + assert(buffev == client->relay); + redsocks_touch_client(client); + + if (process_shutdown_on_write_(client, from, to)) + return; + + if (client->state == ss_connected) + { + /* encrypt and forward data received from client side */ + if (output_size < get_write_hwm(to)) + { + if (input_size) + encrypt_buffer(client, from, to); + if (!(client->client_evshut & EV_READ) && bufferevent_enable(from, EV_READ) == -1) + redsocks_log_errno(client, LOG_ERR, "bufferevent_enable"); + } + } + else + { + redsocks_drop_client(client); + } +} + +static void ss_relay_readcb(struct bufferevent *buffev, void *_arg) +{ + redsocks_client *client = _arg; + struct bufferevent * from = buffev; + struct bufferevent * to = client->client; + size_t input_size = evbuffer_get_contiguous_space(bufferevent_get_input(from)); + size_t output_size = evbuffer_get_length(bufferevent_get_output(to)); + + assert(buffev == client->relay); + redsocks_touch_client(client); + + if (client->state == ss_connected) + { + /* decrypt and forward data to client side */ + if (output_size < get_write_hwm(to)) + { + if (input_size) + decrypt_buffer(client, from, to); + if (bufferevent_enable(from, EV_READ) == -1) + redsocks_log_errno(client, LOG_ERR, "bufferevent_enable"); + } + else + { + if (bufferevent_disable(from, EV_READ) == -1) + redsocks_log_errno(client, LOG_ERR, "bufferevent_disable"); + } + } + else + { + redsocks_drop_client(client); + } +} + +static void ss_relay_connected(struct bufferevent *buffev, void *_arg) +{ + redsocks_client *client = _arg; + + assert(buffev == client->relay); + assert(client->state == ss_new); + redsocks_touch_client(client); + + if (!red_is_socket_connected_ok(buffev)) { + redsocks_log_error(client, LOG_DEBUG, "failed to connect to destination"); + redsocks_drop_client(client); + return; + } + + client->relay_connected = 1; + client->state = ss_connected; + + /* We do not need to detect timeouts any more. + The two peers will handle it. */ + bufferevent_set_timeouts(client->relay, NULL, NULL); + + if (redsocks_start_relay(client)) + // redsocks_start_relay() drops client on failure + return; + /* overwrite theread callback to my function */ + bufferevent_setcb(client->client, ss_client_readcb, + ss_client_writecb, + redsocks_event_error, + client); + bufferevent_setcb(client->relay, ss_relay_readcb, + ss_relay_writecb, + redsocks_event_error, + client); + // Write any data received from client side to relay. + if (evbuffer_get_length(bufferevent_get_input(client->client))) + ss_relay_writecb(client->relay, client); + return; + +} + + +static int ss_connect_relay(redsocks_client *client) +{ + char * interface = client->instance->config.interface; + ss_client *sclient = (void*)(client + 1); + ss_instance * ss = (ss_instance *)(client->instance+1); + ss_header header; + struct timeval tv; + size_t len = 0; + size_t header_len = 0; + char buff[128+sizeof(header)]; + + if (enc_ctx_init(&ss->info, &sclient->e_ctx, 1)) { + log_error(LOG_ERR, "Shadowsocks failed to initialize encryption context."); + redsocks_drop_client(client); + return -1; + } + sclient->e_ctx_init = 1; + if (enc_ctx_init(&ss->info, &sclient->d_ctx, 0)) { + log_error(LOG_ERR, "Shadowsocks failed to initialize decryption context."); + redsocks_drop_client(client); + return -1; + } + sclient->d_ctx_init = 1; + + /* build and send header */ + if (client->destaddr.ss_family == AF_INET) { + struct sockaddr_in * addr = (struct sockaddr_in *)&client->destaddr; + header.v4.addr_type = ss_addrtype_ipv4; + header.v4.addr = addr->sin_addr.s_addr; + header.v4.port = addr->sin_port; + header_len = sizeof(ss_header_ipv4); + } + else if (client->destaddr.ss_family == AF_INET6) { + struct sockaddr_in6 * addr = (struct sockaddr_in6 *)&client->destaddr; + header.v6.addr_type = ss_addrtype_ipv6; + header.v6.addr = addr->sin6_addr; + header.v6.port = addr->sin6_port; + header_len = sizeof(ss_header_ipv6); + } + else { + log_error(LOG_ERR, "Unsupported address family: %d", client->destaddr.ss_family); + redsocks_drop_client(client); + return -1; + } + len += header_len; + size_t sz = sizeof(buff); + if (!ss_encrypt(&sclient->e_ctx, (char *)&header, len, &buff[0], &sz)) { + log_error(LOG_ERR, "Encryption error."); + redsocks_drop_client(client); + return -1; + } + len = sz; + + tv.tv_sec = client->instance->config.timeout; + tv.tv_usec = 0; + client->relay = red_connect_relay_tfo( + interface, + &client->instance->config.relayaddr, + NULL, + ss_relay_connected, + redsocks_event_error, + client, + &tv, + &buff[0], + &sz); + + if (!client->relay) { + redsocks_drop_client(client); + return -1; + } + else if (sz && sz != len) { + log_error(LOG_ERR, "Unexpected length of data sent."); + redsocks_drop_client(client); + return -1; + } + return 0; +} + +static int ss_instance_init(struct redsocks_instance_t *instance) +{ + ss_instance * ss = (ss_instance *)(instance+1); + const redsocks_config *config = &instance->config; + char buf1[RED_INET_ADDRSTRLEN]; + + int valid_cred = ss_is_valid_cred(config->login, config->password); + if (!valid_cred + || (ss->method = enc_init(&ss->info, config->password, config->login), ss->method == -1)) + { + log_error(LOG_ERR, "Invalided encrytion method or password."); + return -1; + } + else + { + log_error(LOG_INFO, "%s @ %s: encryption method: %s", + instance->relay_ss->name, + red_inet_ntop(&instance->config.bindaddr, buf1, sizeof(buf1)), + config->login); + } + return 0; +} + +static void ss_instance_fini(struct redsocks_instance_t *instance) +{ +} + +relay_subsys shadowsocks_subsys = +{ + .name = "shadowsocks", + .payload_len = sizeof(ss_client), + .instance_payload_len = sizeof(ss_instance), + .init = ss_client_init, + .fini = ss_client_fini, + .connect_relay = ss_connect_relay, + .instance_init = ss_instance_init, + .instance_fini = ss_instance_fini, +}; + + +/* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ +/* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ diff --git a/shadowsocks.h b/shadowsocks.h new file mode 100644 index 00000000..8dc956d2 --- /dev/null +++ b/shadowsocks.h @@ -0,0 +1,35 @@ +#ifndef SHADOWSOCKS_H +#define SHADOWSOCKS_H + +enum { + ss_addrtype_ipv4 = 1, + ss_addrtype_domain = 3, + ss_addrtype_ipv6 = 4, +}; + +typedef struct ss_header_ipv4_t { + unsigned char addr_type; + uint32_t addr; + uint16_t port; +} PACKED ss_header_ipv4; + +typedef struct ss_header_ipv6_t { + unsigned char addr_type; + struct in6_addr addr; + uint16_t port; +} PACKED ss_header_ipv6; + +typedef struct ss_header_domain_t { + unsigned char addr_type; + uint8_t length; + char domain[255]; + uint16_t port; +} PACKED ss_header_domain; + +typedef union { + unsigned char addr_type; + ss_header_ipv4 v4; + ss_header_ipv6 v6; +} PACKED ss_header; + +#endif diff --git a/socks4.c b/socks4.c index 865cc77e..faea63eb 100644 --- a/socks4.c +++ b/socks4.c @@ -72,7 +72,7 @@ static void socks4_read_cb(struct bufferevent *buffev, void *_arg) if (client->state == socks4_request_sent) { socks4_reply reply; - if (redsocks_read_expected(client, buffev->input, &reply, sizes_greater_equal, sizeof(reply)) < 0) + if (redsocks_read_expected(client, bufferevent_get_input(buffev), &reply, sizes_greater_equal, sizeof(reply)) < 0) return; client->state = socks4_reply_came; @@ -100,12 +100,13 @@ static struct evbuffer *socks4_mkconnect(redsocks_client *client) // space for \0 comes from socks4_req->login size_t username_len = strlen(username); size_t len = sizeof(socks4_req) + username_len; + struct sockaddr_in * addr = (struct sockaddr_in *)&client->destaddr; socks4_req *req = calloc(1, len); req->ver = socks4_ver; req->cmd = socks4_cmd_connect; - req->port = client->destaddr.sin_port; - req->addr = client->destaddr.sin_addr.s_addr; + req->port = addr->sin_port; + req->addr = addr->sin_addr.s_addr; memcpy(req->login, username, username_len + 1); struct evbuffer *ret = mkevbuffer(req, len); diff --git a/socks5-udp.c b/socks5-udp.c new file mode 100644 index 00000000..19f1f8ef --- /dev/null +++ b/socks5-udp.c @@ -0,0 +1,506 @@ +/* redsocks2 - transparent TCP-to-proxy redirector + * Copyright (C) 2013-2017 Zhuofei Wang + * + * This code is based on redsocks project developed by Leonid Evdokimov. + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "main.h" +#include "utils.h" +#include "log.h" +#include "redudp.h" +#include "redsocks.h" +#include "socks5.h" + +typedef struct socks5_expected_assoc_reply_t { + socks5_reply h; + union { + socks5_addr_ipv4 v4; + socks5_addr_ipv6 v6; + }; +} PACKED socks5_expected_assoc_reply; + +static struct evbuffer* socks5_mkmethods_plain_wrapper(void *p) +{ + int *do_password = p; + return socks5_mkmethods_plain(*do_password); +} + +static struct evbuffer* socks5_mkpassword_plain_wrapper(void *p) +{ + redudp_instance *self = p; + return socks5_mkpassword_plain(self->config.login, self->config.password); +} + +static struct evbuffer* socks5_mkassociate(void *p) +{ + struct sockaddr_storage sa; + memset(&sa, 0, sizeof(sa)); + sa.ss_family = ((const struct sockaddr_storage *)p)->ss_family; + return socks5_mkcommand_plain(socks5_cmd_udp_associate, &sa); +} + +static void socks5_fill_preamble( + socks5_udp_preamble *preamble, + struct sockaddr * addr, + size_t *preamble_len) +{ + preamble->reserved = 0; + preamble->frag_no = 0; /* fragmentation is not supported */ + if (addr->sa_family == AF_INET) { + struct sockaddr_in * in_addr = (struct sockaddr_in *) addr; + preamble->addrtype = socks5_addrtype_ipv4; + preamble->addr.v4.addr = in_addr->sin_addr.s_addr; + preamble->addr.v4.port = in_addr->sin_port; + *preamble_len = 4 + sizeof(preamble->addr.v4); + } + else if (addr->sa_family == AF_INET6) { + struct sockaddr_in6 * in6_addr = (struct sockaddr_in6 *) addr; + preamble->addrtype = socks5_addrtype_ipv6; + memcpy(&preamble->addr.v6, &in6_addr->sin6_addr, sizeof(in6_addr->sin6_addr)); + preamble->addr.v6.port = in6_addr->sin6_port; + *preamble_len = 4 + sizeof(preamble->addr.v6); + } +} + + + +/************************************************************************** + * Logic + * */ + +typedef struct socks5_client_t { + struct event udprelay; + struct sockaddr_storage udprelayaddr; + struct bufferevent *relay; + int ready_fwd; +} socks5_client; + +static void socks5_client_init(redudp_client *client) +{ + socks5_client *socks5client = (void*)(client + 1); + memset(socks5client, 0, sizeof(socks5_client)); +} + +static void socks5_client_fini(redudp_client *client) +{ + socks5_client *socks5client = (void*)(client + 1); + int fd; + + if (event_initialized(&socks5client->udprelay)) { + fd = event_get_fd(&socks5client->udprelay); + if (event_del(&socks5client->udprelay) == -1) + redudp_log_errno(client, LOG_ERR, "event_del"); + close(fd); + } + if (socks5client->relay) { + fd = bufferevent_getfd(socks5client->relay); + bufferevent_free(socks5client->relay); + shutdown(fd, SHUT_RDWR); + close(fd); + } +} + +static int socks5_ready_to_fwd(struct redudp_client_t *client) +{ + socks5_client *socks5client = (void*)(client + 1); + return socks5client->ready_fwd; +} + +static void socks5_forward_pkt(redudp_client *client, struct sockaddr *destaddr, void *buf, size_t pktlen) +{ + socks5_client *socks5client = (void*)(client + 1); + socks5_udp_preamble req; + struct msghdr msg; + struct iovec io[2]; + size_t preamble_len = 0; + + if (socks5client->udprelayaddr.ss_family != AF_INET && socks5client->udprelayaddr.ss_family != AF_INET6) { + redudp_log_errno(client, LOG_WARNING, "Unknown address type %d", + socks5client->udprelayaddr.ss_family); + return; + } + + socks5_fill_preamble(&req, destaddr, &preamble_len); + ssize_t outgoing, fwdlen = pktlen + preamble_len; + memset(&msg, 0, sizeof(msg)); + msg.msg_name = &socks5client->udprelayaddr; + msg.msg_namelen = sizeof(socks5client->udprelayaddr); + msg.msg_iov = io; + msg.msg_iovlen = SIZEOF_ARRAY(io); + + io[0].iov_base = &req; + io[0].iov_len = preamble_len; + io[1].iov_base = buf; + io[1].iov_len = pktlen; + + outgoing = sendmsg(event_get_fd(&socks5client->udprelay), &msg, 0); + if (outgoing == -1) { + redudp_log_errno(client, LOG_WARNING, "sendmsg: Can't forward packet, dropping it"); + return; + } + else if (outgoing != fwdlen) { + redudp_log_error(client, LOG_WARNING, "sendmsg: I was sending %zd bytes, but only %zd were sent.", fwdlen, outgoing); + return; + } +} + +static void socks5_pkt_from_socks(int fd, short what, void *_arg) +{ + redudp_client *client = _arg; + socks5_client *socks5client = (void*)(client + 1); + union { + char buf[MAX_UDP_PACKET_SIZE]; + socks5_udp_preamble header; + } * pkt = client->instance->shared_buff; + ssize_t pktlen, fwdlen; + struct sockaddr_storage udprelayaddr; + + assert(fd == event_get_fd(&socks5client->udprelay)); + + pktlen = red_recv_udp_pkt(fd, pkt->buf, MAX_UDP_PACKET_SIZE, &udprelayaddr, NULL); + if (pktlen == -1) + return; + + if (evutil_sockaddr_cmp((struct sockaddr *)&udprelayaddr, + (struct sockaddr *)&socks5client->udprelayaddr, + 1) != 0) { + char buf[RED_INET_ADDRSTRLEN]; + redudp_log_error(client, LOG_NOTICE, "Got packet from unexpected address %s.", + red_inet_ntop(&udprelayaddr, buf, sizeof(buf))); + return; + } + + if (pkt->header.frag_no != 0) { + // FIXME: does anybody need it? + redudp_log_error(client, LOG_WARNING, "Got fragment #%u. Packet fragmentation is not supported!", + pkt->header.frag_no); + return; + } + + if (pkt->header.addrtype != socks5_addrtype_ipv4 && pkt->header.addrtype != socks5_addrtype_ipv6) { + redudp_log_error(client, LOG_NOTICE, "Got address type #%u.", pkt->header.addrtype); + return; + } + + // Support IPv6 + struct sockaddr_storage src_addr; + size_t header_size = 4; + if (pkt->header.addrtype == socks5_addrtype_ipv4) { + struct sockaddr_in * src = (struct sockaddr_in *)&src_addr; + src->sin_family = AF_INET; + src->sin_addr.s_addr = pkt->header.addr.v4.addr; + src->sin_port = pkt->header.addr.v4.port; + header_size += sizeof(socks5_addr_ipv4); + } + else if (pkt->header.addrtype == socks5_addrtype_ipv6) { + struct sockaddr_in6 * src = (struct sockaddr_in6 *)&src_addr; + src->sin6_family = AF_INET6; + src->sin6_addr = pkt->header.addr.v6.addr; + src->sin6_port = pkt->header.addr.v6.port; + header_size += sizeof(socks5_addr_ipv6); + } + // TODO: Support domain addr + + fwdlen = pktlen - header_size; + redudp_fwd_pkt_to_sender(client, pkt->buf + header_size, fwdlen, &src_addr); +} + + +static size_t calc_assoc_reply_size(int ss_family) { + size_t reply_size = sizeof(socks5_reply); + if (ss_family == AF_INET) + reply_size += sizeof(socks5_addr_ipv4); + else if (ss_family == AF_INET6) + reply_size += sizeof(socks5_addr_ipv6); + return reply_size; +} + +static void socks5_read_assoc_reply(struct bufferevent *buffev, void *_arg) +{ + redudp_client *client = _arg; + socks5_client *socks5client = (void*)(client + 1); + struct evbuffer * input = bufferevent_get_input(buffev); + int fd = -1; + int error; + size_t max_reply_size = calc_assoc_reply_size(AF_INET6); + + // Inspect reply code + { + socks5_reply * reply = (socks5_reply *)evbuffer_pullup(input, -1); + size_t data_size = evbuffer_get_length(input); + + if (data_size < sizeof(socks5_reply)) { + // Wait for more data + bufferevent_setwatermark(buffev, EV_READ, sizeof(socks5_reply), max_reply_size); + return; + } + if (reply->ver != socks5_ver) { + redudp_log_error(client, LOG_NOTICE, "Socks5 server reported unexpected reply version: %u", reply->ver); + goto fail; + } + if (reply->status != socks5_status_succeeded) { + redudp_log_error(client, LOG_NOTICE, "Socks5 server status: \"%s\" (%i)", + socks5_status_to_str(reply->status), reply->status); + goto fail; + } + if (reply->addrtype == socks5_addrtype_ipv4) + max_reply_size = calc_assoc_reply_size(AF_INET); + else if (reply->addrtype == socks5_addrtype_ipv6) + max_reply_size = calc_assoc_reply_size(AF_INET6); + else { + redudp_log_error(client, LOG_NOTICE, "Socks5 server replies bad address type: %d", reply->addrtype); + goto fail; + } + + if (data_size < max_reply_size) { + // Wait for more data + bufferevent_setwatermark(buffev, EV_READ, max_reply_size, max_reply_size); + return; + } + } + // Enough data received + socks5_expected_assoc_reply reply; + int read = evbuffer_remove(bufferevent_get_input(buffev), &reply, max_reply_size); + redudp_log_error(client, LOG_DEBUG, ""); + + if (read != max_reply_size) { + // Should never occur + redudp_log_errno(client, LOG_NOTICE, "evbuffer_remove returned only %i bytes instead of expected %zu", + read, max_reply_size); + goto fail; + } + + // Use relay address instead of address in reply. + // Unless server allocates different IP for UDP association, + // this should work. + // Use port number from UDP association reply as destination + // port. + memcpy(&socks5client->udprelayaddr, + &client->instance->config.relayaddr, + sizeof(struct sockaddr_storage)); + if (reply.h.addrtype == socks5_addrtype_ipv4) { + set_sockaddr_port(&socks5client->udprelayaddr, reply.v4.port); + } + else if (reply.h.addrtype == socks5_addrtype_ipv6) { + set_sockaddr_port(&socks5client->udprelayaddr, reply.v6.port); + } + + fd = socket(socks5client->udprelayaddr.ss_family, SOCK_DGRAM, IPPROTO_UDP); + if (fd == -1) { + redudp_log_errno(client, LOG_ERR, "socket"); + goto fail; + } + + error = evutil_make_socket_nonblocking(fd); + if (error) { + redudp_log_errno(client, LOG_ERR, "evutil_make_socket_nonblocking"); + goto fail; + } + +#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) + error = connect(fd, (struct sockaddr*)&socks5client->udprelayaddr, sizeof(struct sockaddr_in)); +#else + error = connect(fd, (struct sockaddr*)&socks5client->udprelayaddr, sizeof(socks5client->udprelayaddr)); +#endif + if (error) { + redudp_log_errno(client, LOG_NOTICE, "connect"); + goto fail; + } + + event_assign(&socks5client->udprelay, get_event_base(), fd, EV_READ | EV_PERSIST, socks5_pkt_from_socks, client); + error = event_add(&socks5client->udprelay, NULL); + if (error) { + redudp_log_errno(client, LOG_ERR, "event_add"); + goto fail; + } + + socks5client->ready_fwd = 1; + redudp_flush_queue(client); + // TODO: bufferevent_disable ? + return; + +fail: + if (fd != -1) + close(fd); + redudp_drop_client(client); +} + +static void socks5_read_auth_reply(struct bufferevent *buffev, void *_arg) +{ + redudp_client *client = _arg; + socks5_client *socks5client = (void*)(client + 1); + socks5_auth_reply reply; + int read = evbuffer_remove(bufferevent_get_input(buffev), &reply, sizeof(reply)); + int error; + redudp_log_error(client, LOG_DEBUG, ""); + + if (read != sizeof(reply)) { + redudp_log_errno(client, LOG_NOTICE, "evbuffer_remove returned only %i bytes instead of expected %zu", + read, sizeof(reply)); + goto fail; + } + + if (reply.ver != socks5_password_ver || reply.status != socks5_password_passed) { + redudp_log_error(client, LOG_NOTICE, "Socks5 authentication error. Version: %u, error code: %u", + reply.ver, reply.status); + goto fail; + } + + size_t reply_size = calc_assoc_reply_size(client->instance->config.relayaddr.ss_family); + error = redsocks_write_helper_ex_plain( + socks5client->relay, NULL, socks5_mkassociate, &client->destaddr, 0, + sizeof(socks5_reply), reply_size); + if (error) + goto fail; + + replace_readcb(socks5client->relay, socks5_read_assoc_reply); + return; + +fail: + redudp_drop_client(client); +} + + +static void socks5_read_auth_methods(struct bufferevent *buffev, void *_arg) +{ + redudp_client *client = _arg; + socks5_client *socks5client = (void*)(client + 1); + int do_password = socks5_is_valid_cred(client->instance->config.login, client->instance->config.password); + socks5_method_reply reply; + int read = evbuffer_remove(bufferevent_get_input(buffev), &reply, sizeof(reply)); + const char *error = NULL; + int ierror = 0; + redudp_log_error(client, LOG_DEBUG, "do_password: %d", do_password); + + if (read != sizeof(reply)) { + redudp_log_errno(client, LOG_NOTICE, "evbuffer_remove returned only %i bytes instead of expected %zu", + read, sizeof(reply)); + goto fail; + } + + error = socks5_is_known_auth_method(&reply, do_password); + if (error) { + redudp_log_error(client, LOG_NOTICE, "socks5_is_known_auth_method: %s", error); + goto fail; + } + else if (reply.method == socks5_auth_none) { + size_t reply_size = calc_assoc_reply_size(client->instance->config.relayaddr.ss_family); + ierror = redsocks_write_helper_ex_plain( + socks5client->relay, NULL, socks5_mkassociate, &client->destaddr, 0, + sizeof(socks5_reply), reply_size); + if (ierror) + goto fail; + replace_readcb(socks5client->relay, socks5_read_assoc_reply); + } + else if (reply.method == socks5_auth_password) { + ierror = redsocks_write_helper_ex_plain( + socks5client->relay, NULL, socks5_mkpassword_plain_wrapper, client->instance, 0, /* last one is ignored */ + sizeof(socks5_auth_reply), sizeof(socks5_auth_reply)); + if (ierror) + goto fail; + replace_readcb(socks5client->relay, socks5_read_auth_reply); + } + + return; + +fail: + redudp_drop_client(client); +} + +static void socks5_relay_connected(struct bufferevent *buffev, void *_arg) +{ + redudp_client *client = _arg; + socks5_client *socks5client = (void*)(client + 1); + int do_password = socks5_is_valid_cred(client->instance->config.login, client->instance->config.password); + int error; + char relayaddr_str[RED_INET_ADDRSTRLEN]; + redudp_log_error(client, LOG_DEBUG, "via %s", red_inet_ntop(&client->instance->config.relayaddr, relayaddr_str, sizeof(relayaddr_str))); + + if (!red_is_socket_connected_ok(buffev)) { + redudp_log_errno(client, LOG_NOTICE, "red_is_socket_connected_ok"); + goto fail; + } + + error = redsocks_write_helper_ex_plain( + socks5client->relay, NULL, socks5_mkmethods_plain_wrapper, &do_password, 0 /* does not matter */, + sizeof(socks5_method_reply), sizeof(socks5_method_reply)); + if (error) + goto fail; + + replace_readcb(socks5client->relay, socks5_read_auth_methods); + replace_writecb(socks5client->relay, NULL); + //bufferevent_disable(buffev, EV_WRITE); // I don't want to check for writeability. + return; + +fail: + redudp_drop_client(client); +} + +static void socks5_relay_error(struct bufferevent *buffev, short what, void *_arg) +{ + redudp_client *client = _arg; + // TODO: FIXME: Implement me + redudp_log_error(client, LOG_NOTICE, "socks5_relay_error"); + redudp_drop_client(client); +} + + +static void socks5_connect_relay(redudp_client *client) +{ + socks5_client *socks5client = (void*)(client + 1); + socks5client->relay = red_connect_relay( + NULL, + &client->instance->config.relayaddr, + NULL, + socks5_relay_connected, + socks5_relay_error, + client, + NULL); + if (!socks5client->relay) + redudp_drop_client(client); +} + +static int socks5_instance_init(struct redudp_instance_t *instance) +{ + return 0; +} + +static void socks5_instance_fini(struct redudp_instance_t *instance) +{ +} + +udprelay_subsys socks5_udp_subsys = +{ + .name = "socks5", + .payload_len = sizeof(socks5_client), + .instance_payload_len = 0, + .init = socks5_client_init, + .fini = socks5_client_fini, + .instance_init = socks5_instance_init, + .instance_fini = socks5_instance_fini, + .connect_relay = socks5_connect_relay, + .forward_pkt = socks5_forward_pkt, + .ready_to_fwd = socks5_ready_to_fwd, +}; + + +/* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ +/* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ diff --git a/socks5.c b/socks5.c index 4b5456d4..66d1fe5b 100644 --- a/socks5.c +++ b/socks5.c @@ -128,22 +128,38 @@ struct evbuffer *socks5_mkpassword_plain(const char *login, const char *password return mkevbuffer(req, length); } -struct evbuffer *socks5_mkcommand_plain(int socks5_cmd, const struct sockaddr_in *destaddr) +struct evbuffer *socks5_mkcommand_plain(int socks5_cmd, const struct sockaddr_storage *destaddr) { - struct { - socks5_req head; - socks5_addr_ipv4 ip; - } PACKED req; - - assert(destaddr->sin_family == AF_INET); - - req.head.ver = socks5_ver; - req.head.cmd = socks5_cmd; - req.head.reserved = 0; - req.head.addrtype = socks5_addrtype_ipv4; - req.ip.addr = destaddr->sin_addr.s_addr; - req.ip.port = destaddr->sin_port; - return mkevbuffer(&req, sizeof(req)); + if (destaddr->ss_family == AF_INET) { + struct { + socks5_req head; + socks5_addr_ipv4 ip; + } PACKED req; + const struct sockaddr_in * addr = (const struct sockaddr_in *)destaddr; + + req.head.ver = socks5_ver; + req.head.cmd = socks5_cmd; + req.head.reserved = 0; + req.head.addrtype = socks5_addrtype_ipv4; + req.ip.addr = addr->sin_addr.s_addr; + req.ip.port = addr->sin_port; + return mkevbuffer(&req, sizeof(req)); + } + else { + struct { + socks5_req head; + socks5_addr_ipv6 ip; + } PACKED req; + const struct sockaddr_in6 * addr = (const struct sockaddr_in6 *)destaddr; + + req.head.ver = socks5_ver; + req.head.cmd = socks5_cmd; + req.head.reserved = 0; + req.head.addrtype = socks5_addrtype_ipv6; + req.ip.addr = addr->sin6_addr; + req.ip.port = addr->sin6_port; + return mkevbuffer(&req, sizeof(req)); + } } static struct evbuffer *socks5_mkconnect(redsocks_client *client) @@ -182,7 +198,7 @@ static void socks5_read_auth_methods(struct bufferevent *buffev, redsocks_client socks5_method_reply reply; const char *error = NULL; - if (redsocks_read_expected(client, buffev->input, &reply, sizes_equal, sizeof(reply)) < 0) + if (redsocks_read_expected(client, bufferevent_get_input(buffev), &reply, sizes_equal, sizeof(reply)) < 0) return; error = socks5_is_known_auth_method(&reply, socks5->do_password); @@ -208,7 +224,7 @@ static void socks5_read_auth_reply(struct bufferevent *buffev, redsocks_client * { socks5_auth_reply reply; - if (redsocks_read_expected(client, buffev->input, &reply, sizes_equal, sizeof(reply)) < 0) + if (redsocks_read_expected(client, bufferevent_get_input(buffev), &reply, sizes_equal, sizeof(reply)) < 0) return; if (reply.ver != socks5_password_ver) { @@ -228,7 +244,7 @@ static void socks5_read_reply(struct bufferevent *buffev, redsocks_client *clien { socks5_reply reply; - if (redsocks_read_expected(client, buffev->input, &reply, sizes_greater_equal, sizeof(reply)) < 0) + if (redsocks_read_expected(client, bufferevent_get_input(buffev), &reply, sizes_greater_equal, sizeof(reply)) < 0) return; if (reply.ver != socks5_ver) { @@ -290,7 +306,7 @@ static void socks5_read_cb(struct bufferevent *buffev, void *_arg) else if (client->state == socks5_skip_domain) { socks5_addr_ipv4 ipv4; // all socks5_addr*.port are equal uint8_t size; - if (redsocks_read_expected(client, buffev->input, &size, sizes_greater_equal, sizeof(size)) < 0) + if (redsocks_read_expected(client, bufferevent_get_input(buffev), &size, sizes_greater_equal, sizeof(size)) < 0) return; socks5->to_skip = size + sizeof(ipv4.port); redsocks_write_helper( @@ -300,7 +316,7 @@ static void socks5_read_cb(struct bufferevent *buffev, void *_arg) } else if (client->state == socks5_skip_address) { uint8_t data[socks5->to_skip]; - if (redsocks_read_expected(client, buffev->input, data, sizes_greater_equal, socks5->to_skip) < 0) + if (redsocks_read_expected(client, bufferevent_get_input(buffev), data, sizes_greater_equal, socks5->to_skip) < 0) return; redsocks_start_relay(client); } diff --git a/socks5.h b/socks5.h index 5155225a..2949317e 100644 --- a/socks5.h +++ b/socks5.h @@ -36,17 +36,17 @@ typedef struct socks5_addr_ipv4_t { uint16_t port; } PACKED socks5_addr_ipv4; +typedef struct socks5_addr_ipv6_t { + struct in6_addr addr; + uint16_t port; +} PACKED socks5_addr_ipv6; + typedef struct socks5_addr_domain_t { uint8_t size; uint8_t more[1]; /* uint16_t port; */ } PACKED socks5_addr_domain; -typedef struct socks5_addr_ipv6_t { - uint8_t addr[16]; - uint16_t port; -} PACKED socks5_addr_ipv6; - typedef struct socks5_req_t { uint8_t ver; uint8_t cmd; @@ -63,13 +63,19 @@ typedef struct socks5_reply_t { /* socks5_addr_* */ } PACKED socks5_reply; -typedef struct socks5_udp_preabmle_t { +typedef struct socks5_udp_preamble_t { uint16_t reserved; uint8_t frag_no; uint8_t addrtype; /* 0x01 for IPv4 */ /* socks5_addr_* */ - socks5_addr_ipv4 ip; /* I support only IPv4 at the moment */ -} PACKED socks5_udp_preabmle; + union { + socks5_addr_ipv4 v4; + socks5_addr_ipv6 v6; + } addr; +} PACKED socks5_udp_preamble; + +#define SOCKS5_UDP_PREAMBLE_SIZE_V4 (4 + sizeof(socks5_addr_ipv4)) +#define SOCKS5_UDP_PREAMBLE_SIZE_V6 (4 + sizeof(socks5_addr_ipv6)) static const int socks5_reply_maxlen = 512; // as domain name can't be longer than 256 bytes static const int socks5_addrtype_ipv4 = 1; @@ -96,7 +102,7 @@ const char* socks5_is_known_auth_method(socks5_method_reply *reply, int do_passw static const int socks5_cmd_connect = 1; static const int socks5_cmd_bind = 2; static const int socks5_cmd_udp_associate = 3; -struct evbuffer *socks5_mkcommand_plain(int socks5_cmd, const struct sockaddr_in *destaddr); +struct evbuffer *socks5_mkcommand_plain(int socks5_cmd, const struct sockaddr_storage *destaddr); /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ diff --git a/tcpdns.c b/tcpdns.c new file mode 100644 index 00000000..1b2d744a --- /dev/null +++ b/tcpdns.c @@ -0,0 +1,594 @@ +/* redsocks2 - transparent TCP-to-proxy redirector + * Copyright (C) 2013-2017 Zhuofei Wang + * + * This code is based on redsocks project developed by Leonid Evdokimov. + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "base.h" +#include "list.h" +#include "log.h" +#include "parser.h" +#include "main.h" +#include "redsocks.h" +#include "tcpdns.h" +#include "utils.h" + +#define tcpdns_log_error(prio, msg...) \ + redsocks_log_write_plain(__FILE__, __LINE__, __func__, 0, &req->client_addr, &req->instance->config.bindaddr, prio, ## msg) +#define tcpdns_log_errno(prio, msg...) \ + redsocks_log_write_plain(__FILE__, __LINE__, __func__, 1, &req->client_addr, &req->instance->config.bindaddr, prio, ## msg) + +static void tcpdns_fini_instance(tcpdns_instance *instance); +static int tcpdns_fini(); + +#define DNS_QR 0x80 +#define DNS_TC 0x02 +#define DNS_Z 0x40 +#define DNS_RC_MASK 0x0F + +#define DNS_RC_NOERROR 0 +#define DNS_RC_FORMERR 1 +#define DNS_RC_SERVFAIL 2 +#define DNS_RC_NXDOMAIN 3 +#define DNS_RC_NOTIMP 4 +#define DNS_RC_REFUSED 5 +#define DNS_RC_YXDOMAIN 6 +#define DNS_RC_XRRSET 7 +#define DNS_RC_NOTAUTH 8 +#define DNS_RC_NOTZONE 9 + +#define DEFAULT_TIMEOUT_SECONDS 4 + +#define FLAG_TCP_TEST 0x01 +#define FLAG_UDP_TEST 0x02 + +typedef enum tcpdns_state_t { + STATE_NEW, + STATE_REQUEST_SENT, + STATE_RESPONSE_SENT, +} tcpdns_state; + + +/*********************************************************************** + * Logic + */ +static void tcpdns_drop_request(dns_request * req) +{ + int fd; + tcpdns_log_error(LOG_DEBUG, "dropping request @ state: %d", req->state); + if (req->resolver) + { + fd = bufferevent_getfd(req->resolver); + bufferevent_free(req->resolver); + close(fd); + } + + list_del(&req->list); + free(req); +} + +static inline void tcpdns_update_delay(dns_request * req, int delay) +{ + if (req->delay) + * req->delay = delay; +} + +static void tcpdns_readcb(struct bufferevent *from, void *_arg) +{ + dns_request * req = _arg; + union { + short len; + char raw[4096]; + } buff; + struct timeval tv; + assert(from == req->resolver); + size_t input_size = evbuffer_get_length(bufferevent_get_input(from)); + size_t read_size; + + tcpdns_log_error(LOG_DEBUG, "response size: %zu", input_size); + + if (input_size == 0 || input_size > sizeof(buff)) + // EOF or response is too large. Drop it. + goto finish; + + if (req->state == STATE_REQUEST_SENT + && input_size > 2 // At least length indicator is received + ) + { + // FIXME: + // suppose we got all data in one read + read_size = bufferevent_read(from, &buff, sizeof(buff)); + if (read_size > (2 + sizeof(dns_header))) + { + dns_header * dh = (dns_header *)&buff.raw[2]; + switch (dh->ra_z_rcode & DNS_RC_MASK) { + case DNS_RC_NOERROR: + case DNS_RC_FORMERR: + case DNS_RC_NXDOMAIN: + { + int fd = event_get_fd(req->instance->listener); + if (sendto(fd, &buff.raw[2], read_size - 2, 0, + (struct sockaddr*)&req->client_addr, + sizeof(req->client_addr)) != read_size - 2) { + tcpdns_log_errno(LOG_ERR, "sendto"); + } + req->state = STATE_RESPONSE_SENT; + // calculate and update DNS resolver's delay + gettimeofday(&tv, 0); + timersub(&tv, &req->req_time, &tv); + tcpdns_update_delay(req, tv.tv_sec*1000+tv.tv_usec/1000); + } + break; + default: + // panalize server + tcpdns_update_delay(req, (req->instance->config.timeout + 1) * 1000); + } + } + } +finish: + tcpdns_drop_request(req); +} + + +static void tcpdns_connected(struct bufferevent *buffev, void *_arg) +{ + dns_request * req = _arg; + assert(buffev == req->resolver); + struct timeval tv, tv2; + + if (!red_is_socket_connected_ok(buffev)) + { + tcpdns_log_error(LOG_DEBUG, "failed to connect to destination"); + tcpdns_drop_request(req); + return; + } + + if (req->state != STATE_NEW) + // Nothing to write + return; + + // Write dns request to DNS resolver and shutdown connection + uint16_t len = htons((uint16_t)req->data_len); + if (bufferevent_write(buffev, &len, sizeof(uint16_t)) == -1 + || bufferevent_write(buffev, &req->data.raw, req->data_len) == -1) + { + tcpdns_log_errno(LOG_ERR, "bufferevent_write"); + tcpdns_drop_request(req); + return; + } + + // Set timeout for read with time left since connection setup. + gettimeofday(&tv, 0); + timersub(&tv, &req->req_time, &tv); + tv2.tv_sec = req->instance->config.timeout; + tv2.tv_usec = 0; + timersub(&tv2, &tv, &tv); + if (tv.tv_sec > 0 || tv.tv_usec > 0) { + bufferevent_set_timeouts(buffev, &tv, NULL); + // Allow reading response + bufferevent_enable(buffev, EV_READ); + req->state = STATE_REQUEST_SENT; + } + else { + tcpdns_update_delay(req, tv2.tv_sec * 1000); + tcpdns_drop_request(req); + } +} + + +static void tcpdns_event_error(struct bufferevent *buffev, short what, void *_arg) +{ + dns_request * req = _arg; + int saved_errno = errno; + assert(buffev == req->resolver); + + tcpdns_log_errno(LOG_DEBUG, "errno(%d), what: " event_fmt_str, + saved_errno, event_fmt(what)); + + if (req->state == STATE_NEW + && what == (BEV_EVENT_WRITING | BEV_EVENT_TIMEOUT)) + { + tcpdns_update_delay(req, -1); + } + else if (saved_errno == ECONNRESET) { + // If connect is reset, try to not use this DNS server next time. + tcpdns_update_delay(req, (req->instance->config.timeout + 1) * 1000); + } + tcpdns_drop_request(req); +} + +static struct sockaddr_storage * choose_tcpdns(tcpdns_instance * instance, int **delay) +{ + static int n = 0; + log_error(LOG_DEBUG, "Dealy of TCP DNS resolvers: %d, %d", instance->tcp1_delay_ms, instance->tcp2_delay_ms); + if (instance->config.tcpdns1 && instance->config.tcpdns2) + { + if (instance->tcp1_delay_ms <= 0 + && instance->tcp2_delay_ms <= 0) + { + // choose one + n += 1; + if (n%2) + goto return_tcp1; + else + goto return_tcp2; + } + if (instance->tcp1_delay_ms > instance->tcp2_delay_ms) + { + if (instance->tcp2_delay_ms < 0) + goto return_tcp1; + else + goto return_tcp2; + } + else + { + if (instance->tcp1_delay_ms < 0) + goto return_tcp2; + else + goto return_tcp1; + } + } + if (instance->config.tcpdns1) + goto return_tcp1; + if (instance->config.tcpdns2) + goto return_tcp2; + + * delay = NULL; + return NULL; + +return_tcp1: + * delay = &instance->tcp1_delay_ms; + return &instance->config.tcpdns1_addr; + +return_tcp2: + * delay = &instance->tcp2_delay_ms; + return &instance->config.tcpdns2_addr; + +} + +static void tcpdns_pkt_from_client(int fd, short what, void *_arg) +{ + tcpdns_instance *self = _arg; + dns_request * req = NULL; + struct timeval tv; + struct sockaddr_storage * destaddr; + ssize_t pktlen; + + assert(fd == event_get_fd(self->listener)); + /* allocate and initialize request structure */ + req = (dns_request *)calloc(sizeof(dns_request), 1); + if (!req) + { + log_error(LOG_ERR, "Out of memeory."); + return; + } + req->instance = self; + req->state = STATE_NEW; + gettimeofday(&req->req_time, 0); + pktlen = red_recv_udp_pkt(fd, req->data.raw, sizeof(req->data.raw), &req->client_addr, NULL); + if (pktlen == -1) + { + free(req); + return; + } + if (pktlen <= sizeof(dns_header)) + { + tcpdns_log_error(LOG_INFO, "incomplete DNS request"); + free(req); + return; + } + req->data_len = pktlen; + + if ( (req->data.header.qr_opcode_aa_tc_rd & DNS_QR) == 0 /* query */ + && (req->data.header.ra_z_rcode & DNS_Z) == 0 /* Z is Zero */ + && req->data.header.qdcount /* some questions */ + && !req->data.header.ancount && !req->data.header.nscount + ) + { + tv.tv_sec = self->config.timeout; + tv.tv_usec = 0; + + destaddr = choose_tcpdns(self, &req->delay); + if (!destaddr) + { + tcpdns_log_error(LOG_WARNING, "No valid DNS resolver configured"); + free(req); + return; + } + /* connect to target directly without going through proxy */ + req->resolver = red_connect_relay(NULL, destaddr, + tcpdns_readcb, tcpdns_connected, tcpdns_event_error, req, + &tv); + if (req->resolver) + list_add(&req->list, &self->requests); + else + { + tcpdns_log_error(LOG_INFO, "Failed to setup connection to DNS resolver"); + free(req); + } + } + else + { + tcpdns_log_error(LOG_INFO, "malformed DNS request"); + free(req); + } +} + +/*********************************************************************** + * DNS Resolver Delay Checking + */ +static void check_udpdns_delay() +{ +} + +static void check_tcpdns_delay() +{ +} + +static void check_dns_delay() +{ + check_udpdns_delay(); + check_tcpdns_delay(); +} + + +/*********************************************************************** + * Init / shutdown + */ +static parser_entry tcpdns_entries[] = +{ + { .key = "bind", .type = pt_pchar }, + { .key = "tcpdns1", .type = pt_pchar }, + { .key = "tcpdns2", .type = pt_pchar }, + { .key = "timeout", .type = pt_uint16 }, + { } +}; + +static list_head instances = LIST_HEAD_INIT(instances); + +static int tcpdns_onenter(parser_section *section) +{ + tcpdns_instance *instance = calloc(1, sizeof(*instance)); + if (!instance) { + parser_error(section->context, "Not enough memory"); + return -1; + } + + INIT_LIST_HEAD(&instance->list); + INIT_LIST_HEAD(&instance->requests); + struct sockaddr_in * addr = (struct sockaddr_in *)&instance->config.bindaddr; + addr->sin_family = AF_INET; + addr->sin_addr.s_addr = htonl(INADDR_LOOPBACK); + addr->sin_port = htons(53); + + for (parser_entry *entry = §ion->entries[0]; entry->key; entry++) + entry->addr = + (strcmp(entry->key, "bind") == 0) ? (void*)&instance->config.bind: + (strcmp(entry->key, "tcpdns1") == 0) ? (void*)&instance->config.tcpdns1 : + (strcmp(entry->key, "tcpdns2") == 0) ? (void*)&instance->config.tcpdns2 : + (strcmp(entry->key, "timeout") == 0) ? (void*)&instance->config.timeout : + NULL; + section->data = instance; + return 0; +} + +static int tcpdns_onexit(parser_section *section) +{ + const char *err = NULL; + tcpdns_instance *instance = section->data; + + section->data = NULL; + for (parser_entry *entry = §ion->entries[0]; entry->key; entry++) + entry->addr = NULL; + + // Parse and update bind address and relay address + if (instance->config.bind) { + struct sockaddr * addr = (struct sockaddr *)&instance->config.bindaddr; + int addr_size = sizeof(instance->config.bindaddr); + if (evutil_parse_sockaddr_port(instance->config.bind, addr, &addr_size)) + err = "invalid bind address"; + } + if (!err && instance->config.tcpdns1) { + struct sockaddr * addr = (struct sockaddr *)&instance->config.tcpdns1_addr; + int addr_size = sizeof(instance->config.tcpdns1_addr); + if (evutil_parse_sockaddr_port(instance->config.tcpdns1, addr, &addr_size)) + err = "invalid tcpdns1 address"; + else if (addr->sa_family == AF_INET && ((struct sockaddr_in *)addr)->sin_port == 0) + ((struct sockaddr_in *)addr)->sin_port = htons(53); + else if (addr->sa_family == AF_INET6 && ((struct sockaddr_in6 *)addr)->sin6_port == 0) + ((struct sockaddr_in6 *)addr)->sin6_port = htons(53); + } + if (!err && instance->config.tcpdns2) { + struct sockaddr * addr = (struct sockaddr *)&instance->config.tcpdns2_addr; + int addr_size = sizeof(instance->config.tcpdns2_addr); + if (evutil_parse_sockaddr_port(instance->config.tcpdns2, addr, &addr_size)) + err = "invalid tcpdns2 address"; + else if (addr->sa_family == AF_INET && ((struct sockaddr_in *)addr)->sin_port == 0) + ((struct sockaddr_in *)addr)->sin_port = htons(53); + else if (addr->sa_family == AF_INET6 && ((struct sockaddr_in6 *)addr)->sin6_port == 0) + ((struct sockaddr_in6 *)addr)->sin6_port = htons(53); + } + + + if (instance->config.tcpdns1 == NULL && instance->config.tcpdns2 == NULL) + err = "At least one TCP DNS resolver must be configured."; + + if (err) + parser_error(section->context, "%s", err); + else + list_add(&instance->list, &instances); + // If timeout is not configured or is configured as zero, use default timeout. + if (instance->config.timeout == 0) + instance->config.timeout = DEFAULT_TIMEOUT_SECONDS; + return err ? -1 : 0; +} + +static int tcpdns_init_instance(tcpdns_instance *instance) +{ + /* FIXME: tcpdns_fini_instance is called in case of failure, this + * function will remove instance from instances list - result + * looks ugly. + */ + int error; + int fd = -1; + int bindaddr_len = 0; + char buf1[RED_INET_ADDRSTRLEN]; + + fd = socket(instance->config.bindaddr.ss_family, SOCK_DGRAM, IPPROTO_UDP); + if (fd == -1) { + log_errno(LOG_ERR, "socket"); + goto fail; + } + if (apply_reuseport(fd)) + log_error(LOG_WARNING, "Continue without SO_REUSEPORT enabled"); + +#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) + bindaddr_len = instance->config.bindaddr.ss_len > 0 ? instance->config.bindaddr.ss_len : sizeof(instance->config.bindaddr); +#else + bindaddr_len = sizeof(instance->config.bindaddr); +#endif + error = bind(fd, (struct sockaddr*)&instance->config.bindaddr, bindaddr_len); + if (error) { + log_errno(LOG_ERR, "bind"); + goto fail; + } + + error = evutil_make_socket_nonblocking(fd); + if (error) { + log_errno(LOG_ERR, "evutil_make_socket_nonblocking"); + goto fail; + } + + instance->listener = event_new(get_event_base(), fd, EV_READ | EV_PERSIST, tcpdns_pkt_from_client, instance); + if (!instance->listener) { + log_errno(LOG_ERR, "event_new"); + goto fail; + } + error = event_add(instance->listener, NULL); + if (error) + { + log_errno(LOG_ERR, "event_add"); + goto fail; + } + + log_error(LOG_INFO, "tcpdns @ %s", + red_inet_ntop(&instance->config.bindaddr, buf1, sizeof(buf1))); + return 0; + +fail: + tcpdns_fini_instance(instance); + + if (fd != -1 && close(fd) != 0) + log_errno(LOG_WARNING, "close"); + + return -1; +} + +/* Drops instance completely, freeing its memory and removing from + * instances list. + */ +static void tcpdns_fini_instance(tcpdns_instance *instance) +{ + if (instance->listener) { + if (event_del(instance->listener) != 0) + log_errno(LOG_WARNING, "event_del"); + if (close(event_get_fd(instance->listener)) != 0) + log_errno(LOG_WARNING, "close"); + event_free(instance->listener); + } + + list_del(&instance->list); + + memset(instance, 0, sizeof(*instance)); + free(instance); +} + +static int tcpdns_init() +{ + tcpdns_instance *tmp, *instance = NULL; + + list_for_each_entry_safe(instance, tmp, &instances, list) { + if (tcpdns_init_instance(instance) != 0) + goto fail; + } + + return 0; + +fail: + tcpdns_fini(); + return -1; +} + +static int tcpdns_fini() +{ + tcpdns_instance *tmp, *instance = NULL; + + list_for_each_entry_safe(instance, tmp, &instances, list) + tcpdns_fini_instance(instance); + + return 0; +} + +static void tcpdns_dump_instance(tcpdns_instance *instance) +{ + char buf1[RED_INET_ADDRSTRLEN]; + + log_error(LOG_INFO, "Dumping data for instance (tcpdns @ %s):", + red_inet_ntop(&instance->config.bindaddr, buf1, sizeof(buf1))); + log_error(LOG_INFO, "Delay of TCP DNS [%s]: %dms", + red_inet_ntop(&instance->config.tcpdns1_addr, buf1, sizeof(buf1)), + instance->tcp1_delay_ms); + log_error(LOG_INFO, "Delay of TCP DNS [%s]: %dms", + red_inet_ntop(&instance->config.tcpdns2_addr, buf1, sizeof(buf1)), + instance->tcp2_delay_ms); + log_error(LOG_INFO, "End of data dumping."); +} + + +static void tcpdns_debug_dump() +{ + tcpdns_instance *instance = NULL; + + list_for_each_entry(instance, &instances, list) + tcpdns_dump_instance(instance); +} + +static parser_section tcpdns_conf_section = +{ + .name = "tcpdns", + .entries = tcpdns_entries, + .onenter = tcpdns_onenter, + .onexit = tcpdns_onexit +}; + +app_subsys tcpdns_subsys = +{ + .init = tcpdns_init, + .fini = tcpdns_fini, + .dump = tcpdns_debug_dump, + .conf_section = &tcpdns_conf_section, +}; + +/* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ +/* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ diff --git a/tcpdns.h b/tcpdns.h new file mode 100644 index 00000000..bbb8b1c5 --- /dev/null +++ b/tcpdns.h @@ -0,0 +1,54 @@ +#ifndef TCPDNS_H +#define TCPDNS_H + +typedef struct tcpdns_config_t { + struct sockaddr_storage bindaddr; + struct sockaddr_storage tcpdns1_addr; + struct sockaddr_storage tcpdns2_addr; + char *bind; + char *tcpdns1; + char *tcpdns2; + uint16_t timeout; /* timeout value for DNS response*/ +} tcpdns_config; + +typedef struct tcpdns_instance_t { + list_head list; + tcpdns_config config; + struct event * listener; + list_head requests; + // Data for DNS resolver status tracking/checking + int udp1_delay_ms; + int udp2_delay_ms; + int tcp1_delay_ms; + int tcp2_delay_ms; +} tcpdns_instance; + + +typedef struct dns_header_t { + uint16_t id; + uint8_t qr_opcode_aa_tc_rd; + uint8_t ra_z_rcode; + uint16_t qdcount; + uint16_t ancount; + uint16_t nscount; + uint16_t arcount; +} PACKED dns_header; + + +typedef struct dns_request_t { + list_head list; + tcpdns_instance * instance; + short state; + int flags; + struct bufferevent* resolver; + struct sockaddr_storage client_addr; + struct timeval req_time; + int * delay; + size_t data_len; + union { + char raw[513]; // DNS request longer than 512 should go over TCP. + dns_header header; + } data; +} dns_request; + +#endif /* TCPDNS_H */ diff --git a/tools/git-repack.sh b/tools/git-repack.sh index f5fd2229..799ec6e1 100755 --- a/tools/git-repack.sh +++ b/tools/git-repack.sh @@ -3,9 +3,9 @@ set -o errexit set -o xtrace -user="darkk" +user="semigodking" proj="redsocks" -versions="0.1 0.2 0.3 0.4" +versions="0.1 0.2 0.3 0.4 0.51 0.60 0.65" for file in `python -c "import urllib2, json; print '\n'.join(d['name'] for d in json.load(urllib2.urlopen('https://api.github.com/repos/${user}/${proj}/downloads')))"`; do touch "$file.uploaded" diff --git a/utils.c b/utils.c index 9c878e6d..4dd3bb16 100644 --- a/utils.c +++ b/utils.c @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ - +#include #include #include #include @@ -22,229 +22,628 @@ #include #include #include +#include +#include +#include "config.h" +#include "main.h" #include "log.h" #include "base.h" #include "utils.h" #include "redsocks.h" // for redsocks_close #include "libc-compat.h" -int red_recv_udp_pkt(int fd, char *buf, size_t buflen, struct sockaddr_in *inaddr, struct sockaddr_in *toaddr) -{ - socklen_t addrlen = sizeof(*inaddr); - ssize_t pktlen; - struct msghdr msg; - struct iovec io; - char control[1024]; - - memset(&msg, 0, sizeof(msg)); - msg.msg_name = inaddr; - msg.msg_namelen = sizeof(*inaddr); - msg.msg_iov = &io; - msg.msg_iovlen = 1; - msg.msg_control = control; - msg.msg_controllen = sizeof(control); - io.iov_base = buf; - io.iov_len = buflen; - - pktlen = recvmsg(fd, &msg, 0); - if (pktlen == -1) { - log_errno(LOG_WARNING, "recvfrom"); - return -1; - } - - if (toaddr) { - memset(toaddr, 0, sizeof(*toaddr)); - for (struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { - if ( - cmsg->cmsg_level == SOL_IP && - cmsg->cmsg_type == IP_ORIGDSTADDR && - cmsg->cmsg_len >= CMSG_LEN(sizeof(*toaddr)) - ) { - struct sockaddr_in* cmsgaddr = (struct sockaddr_in*)CMSG_DATA(cmsg); - char buf[RED_INET_ADDRSTRLEN]; - log_error(LOG_DEBUG, "IP_ORIGDSTADDR: %s", red_inet_ntop(cmsgaddr, buf, sizeof(buf))); - memcpy(toaddr, cmsgaddr, sizeof(*toaddr)); - } - else { - log_error(LOG_WARNING, "unexepcted cmsg (level,type) = (%d,%d)", - cmsg->cmsg_level, cmsg->cmsg_type); - } - } - if (toaddr->sin_family != AF_INET) { - log_error(LOG_WARNING, "(SOL_IP, IP_ORIGDSTADDR) not found"); - return -1; - } - } - - if (addrlen != sizeof(*inaddr)) { - log_error(LOG_WARNING, "unexpected address length %u instead of %zu", addrlen, sizeof(*inaddr)); - return -1; - } - - if (pktlen >= buflen) { - char buf[RED_INET_ADDRSTRLEN]; - log_error(LOG_WARNING, "wow! Truncated udp packet of size %zd from %s! impossible! dropping it...", - pktlen, red_inet_ntop(inaddr, buf, sizeof(buf))); - return -1; - } - - return pktlen; + +#define addr_size(addr) (((struct sockaddr *)addr)->sa_family == AF_INET ? sizeof(struct sockaddr_in): \ + ((struct sockaddr *)addr)->sa_family == AF_INET6 ? sizeof(struct sockaddr_in6): sizeof(struct sockaddr_storage)) + +int red_recv_udp_pkt( + int fd, + char *buf, + size_t buflen, + struct sockaddr_storage *inaddr, + struct sockaddr_storage *toaddr) +{ + ssize_t pktlen; + struct msghdr msg; + struct iovec io; + char control[1024]; + + memset(&msg, 0, sizeof(msg)); + msg.msg_name = inaddr; + msg.msg_namelen = sizeof(*inaddr); + msg.msg_iov = &io; + msg.msg_iovlen = 1; + msg.msg_control = control; + msg.msg_controllen = sizeof(control); + io.iov_base = buf; + io.iov_len = buflen; + + pktlen = recvmsg(fd, &msg, 0); + if (pktlen == -1) { + log_errno(LOG_WARNING, "recvfrom"); + return -1; + } + + if (pktlen >= buflen) { + char buf[RED_INET_ADDRSTRLEN]; + log_error(LOG_WARNING, "wow! Truncated udp packet of size %zd from %s! impossible! dropping it...", + pktlen, red_inet_ntop(inaddr, buf, sizeof(buf))); + return -1; + } + + if (toaddr) { + memset(toaddr, 0, sizeof(*toaddr)); + for (struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { + if ( + ((cmsg->cmsg_level == SOL_IP && cmsg->cmsg_type == IP_ORIGDSTADDR) +#ifdef SOL_IPV6 + || (cmsg->cmsg_level == SOL_IPV6 && cmsg->cmsg_type == IPV6_ORIGDSTADDR) +#endif + ) && + (cmsg->cmsg_len == CMSG_LEN(sizeof(struct sockaddr_in)) + || cmsg->cmsg_len == CMSG_LEN(sizeof(struct sockaddr_in6))) && + cmsg->cmsg_len <= CMSG_LEN(sizeof(*toaddr)) + ) { + struct sockaddr* cmsgaddr = (struct sockaddr*)CMSG_DATA(cmsg); + if (cmsgaddr->sa_family == AF_INET) { + memcpy(toaddr, cmsgaddr, sizeof(struct sockaddr_in)); + } + else if (cmsgaddr->sa_family == AF_INET6) { + memcpy(toaddr, cmsgaddr, sizeof(struct sockaddr_in6)); + } + else { + log_error(LOG_WARNING, + "unexepcted socket address type: %d", + cmsgaddr->sa_family); + } + break; + } +#if defined(__OpenBSD__) || defined(__NetBSD__) + //TODO: support IPv6 + else if (cmsg->cmsg_type == IP_RECVDSTADDR || cmsg->cmsg_type == IP_RECVDSTPORT){ + struct sockaddr* cmsgaddr = (struct sockaddr*)CMSG_DATA(cmsg); + toaddr->ss_family=AF_INET; + struct sockaddr_in *toaddr_in = (struct sockaddr_in *)toaddr; + if (cmsg->cmsg_type == IP_RECVDSTADDR) { + memcpy(&toaddr_in->sin_addr, cmsgaddr, sizeof(struct in_addr)); + } else if (cmsg->cmsg_type == IP_RECVDSTPORT) { + memcpy(&toaddr_in->sin_port, cmsgaddr, sizeof(in_port_t)); + } + } +#endif + else { + log_error(LOG_WARNING, "unexepcted cmsg (level,type) = (%d,%d)", + cmsg->cmsg_level, cmsg->cmsg_type); + } + } + if (toaddr->ss_family == 0) { +#if !defined(__FreeBSD__) && !defined(__OpenBSD__) && !defined(__NetBSD__) + log_error(LOG_WARNING, "(SOL_IP, IP_ORIGDSTADDR) not found"); + return -1; +#endif + } + } + + return pktlen; } uint32_t red_randui32() { - uint32_t ret; - evutil_secure_rng_get_bytes(&ret, sizeof(ret)); - return ret; + uint32_t ret; + evutil_secure_rng_get_bytes(&ret, sizeof(ret)); + return ret; } time_t redsocks_time(time_t *t) { - time_t retval; - retval = time(t); - if (retval == ((time_t) -1)) - log_errno(LOG_WARNING, "time"); - return retval; + time_t retval; + retval = time(t); + if (retval == ((time_t) -1)) + log_errno(LOG_WARNING, "time"); + return retval; } -char *redsocks_evbuffer_readline(struct evbuffer *buf) +struct bufferevent* red_prepare_relay(const char *ifname, + int sa_family, + bufferevent_data_cb readcb, + bufferevent_data_cb writecb, + bufferevent_event_cb errorcb, + void *cbarg) { -#if _EVENT_NUMERIC_VERSION >= 0x02000000 - return evbuffer_readln(buf, NULL, EVBUFFER_EOL_CRLF); -#else - return evbuffer_readline(buf); + struct bufferevent *retval = NULL; + int relay_fd = -1; + int error; + + relay_fd = socket(sa_family, SOCK_STREAM, IPPROTO_TCP); + if (relay_fd == -1) { + log_errno(LOG_ERR, "socket"); + goto fail; + } + if (ifname && strlen(ifname)) { +#ifdef USE_PF // BSD + error = setsockopt(relay_fd, SOL_SOCKET, IP_RECVIF, ifname, strlen(ifname)); +#else // Linux + error = setsockopt(relay_fd, SOL_SOCKET, SO_BINDTODEVICE, ifname, strlen(ifname)); #endif + if (error) { + log_errno(LOG_ERR, "setsockopt"); + goto fail; + } + } + error = evutil_make_socket_nonblocking(relay_fd); + if (error) { + log_errno(LOG_ERR, "evutil_make_socket_nonblocking"); + goto fail; + } + + retval = bufferevent_socket_new(get_event_base(), relay_fd, 0); + if (!retval) { + log_errno(LOG_ERR, "bufferevent_socket_new"); + goto fail; + } + + bufferevent_setcb(retval, readcb, writecb, errorcb, cbarg); + if (writecb) { + error = bufferevent_enable(retval, EV_WRITE); // we wait for connection... + if (error) { + log_errno(LOG_ERR, "bufferevent_enable"); + goto fail; + } + } + + if (apply_tcp_keepalive(relay_fd)) + goto fail; + + return retval; + +fail: + if (retval){ + bufferevent_disable(retval, EV_READ|EV_WRITE); + bufferevent_free(retval); + } + if (relay_fd != -1) + redsocks_close(relay_fd); + return NULL; } -struct bufferevent* red_connect_relay(struct sockaddr_in *addr, evbuffercb writecb, everrorcb errorcb, void *cbarg) +struct bufferevent* red_connect_relay(const char *ifname, + struct sockaddr_storage *addr, + bufferevent_data_cb readcb, + bufferevent_data_cb writecb, + bufferevent_event_cb errorcb, + void *cbarg, + const struct timeval *timeout_write) { - struct bufferevent *retval = NULL; - int relay_fd = -1; - int error; - - relay_fd = socket(AF_INET, SOCK_STREAM, 0); - if (relay_fd == -1) { - log_errno(LOG_ERR, "socket"); - goto fail; - } + struct bufferevent *retval = NULL; + int relay_fd = -1; + int error; + + retval = red_prepare_relay(ifname, addr->ss_family, readcb, writecb, errorcb, cbarg); + if (retval) { + relay_fd = bufferevent_getfd(retval); + if (timeout_write) + bufferevent_set_timeouts(retval, NULL, timeout_write); + + // error = bufferevent_socket_connect(retval, addr, sizeof(*addr)); + // if (error) { + error = connect(relay_fd, (struct sockaddr *)addr, addr_size(addr)); + if (error && errno != EINPROGRESS) { + log_errno(LOG_NOTICE, "connect"); + goto fail; + } + } + return retval; - error = fcntl_nonblock(relay_fd); - if (error) { - log_errno(LOG_ERR, "fcntl"); - goto fail; - } - - if (apply_tcp_keepalive(relay_fd)) - goto fail; +fail: + if (retval) { + bufferevent_disable(retval, EV_READ|EV_WRITE); + bufferevent_free(retval); + } + if (relay_fd != -1) + redsocks_close(relay_fd); + return NULL; +} - error = connect(relay_fd, (struct sockaddr*)addr, sizeof(*addr)); - if (error && errno != EINPROGRESS) { - log_errno(LOG_NOTICE, "connect"); - goto fail; - } +#if defined(ENABLE_HTTPS_PROXY) +struct bufferevent* red_connect_relay_ssl(const char *ifname, + struct sockaddr_storage *addr, + SSL * ssl, + bufferevent_data_cb readcb, + bufferevent_data_cb writecb, + bufferevent_event_cb errorcb, + void *cbarg, + const struct timeval *timeout_write) +{ + struct bufferevent *retval = NULL; + struct bufferevent *underlying = NULL; + int relay_fd = -1; + int error; + + underlying = red_prepare_relay(ifname, addr->ss_family, NULL, NULL, NULL, NULL); + if (!underlying) + goto fail; + relay_fd = bufferevent_getfd(underlying); + if (timeout_write) + bufferevent_set_timeouts(underlying, NULL, timeout_write); + + error = connect(relay_fd, (struct sockaddr *)addr, addr_size(addr)); + if (error && errno != EINPROGRESS) { + log_errno(LOG_NOTICE, "connect"); + goto fail; + } + retval = bufferevent_openssl_filter_new(bufferevent_get_base(underlying), + underlying, + ssl, + BUFFEREVENT_SSL_CONNECTING, + BEV_OPT_DEFER_CALLBACKS); + if (!retval) { + log_errno(LOG_NOTICE, "bufferevent_openssl_filter_new"); + goto fail; + } + if (timeout_write) + bufferevent_set_timeouts(retval, NULL, timeout_write); + + bufferevent_setcb(retval, readcb, writecb, errorcb, cbarg); + if (writecb) { + error = bufferevent_enable(retval, EV_WRITE); // we wait for connection... + if (error) { + log_errno(LOG_ERR, "bufferevent_enable"); + goto fail; + } + } + return retval; - retval = bufferevent_new(relay_fd, NULL, writecb, errorcb, cbarg); - if (!retval) { - log_errno(LOG_ERR, "bufferevent_new"); - goto fail; - } +fail: + if (retval) { + bufferevent_disable(retval, EV_READ|EV_WRITE); + bufferevent_free(retval); + } + if (underlying) { + bufferevent_disable(underlying, EV_READ|EV_WRITE); + bufferevent_free(underlying); + } + if (relay_fd != -1) + redsocks_close(relay_fd); + return NULL; +} +#endif - error = bufferevent_enable(retval, EV_WRITE); // we wait for connection... - if (error) { - log_errno(LOG_ERR, "bufferevent_enable"); - goto fail; - } +struct bufferevent* red_connect_relay_tfo(const char *ifname, + struct sockaddr_storage *addr, + bufferevent_data_cb readcb, + bufferevent_data_cb writecb, + bufferevent_event_cb errorcb, + void *cbarg, + const struct timeval *timeout_write, + void *data, + size_t *len) +{ + struct bufferevent *retval = NULL; + int relay_fd = -1; + int error; + + retval = red_prepare_relay(ifname, addr->ss_family, readcb, writecb, errorcb, cbarg); + if (retval) { + relay_fd = bufferevent_getfd(retval); + if (timeout_write) + bufferevent_set_timeouts(retval, NULL, timeout_write); + +#ifdef MSG_FASTOPEN + size_t s = sendto(relay_fd, data, * len, MSG_FASTOPEN, + (struct sockaddr *)addr, addr_size(addr) + ); + if (s == -1) { + if (errno == EINPROGRESS || errno == EAGAIN + || errno == EWOULDBLOCK) { + // Remote server doesn't support tfo or it's the first connection to the server. + // Connection will automatically fall back to conventional TCP. + log_error(LOG_DEBUG, "TFO: no cookie"); + // write data to evbuffer so that data can be sent when connection is set up + if (bufferevent_write(retval, data, *len) != 0) { + log_errno(LOG_NOTICE, "bufferevent_write"); + goto fail; + } + return retval; + } else if (errno == EOPNOTSUPP || errno == EPROTONOSUPPORT || + errno == ENOPROTOOPT) { + // Disable fast open as it's not supported + log_error(LOG_DEBUG, "TFO: not support"); + goto fallback; + } else { + log_errno(LOG_NOTICE, "sendto"); + goto fail; + } + } + else { + log_error(LOG_DEBUG, "TFO: cookie found"); + *len = s; // data is put into socket buffer + return retval; + } +fallback: +#endif - return retval; + error = connect(relay_fd, (struct sockaddr *)addr, addr_size(addr)); + if (error && errno != EINPROGRESS) { + log_errno(LOG_NOTICE, "connect"); + goto fail; + } + // write data to evbuffer so that data can be sent when connection is set up + if (bufferevent_write(retval, data, *len) != 0) { + log_errno(LOG_NOTICE, "bufferevent_write"); + goto fail; + } + } + return retval; fail: - if (relay_fd != -1) - redsocks_close(relay_fd); - if (retval) - bufferevent_free(retval); - return NULL; + if (retval) { + bufferevent_disable(retval, EV_READ|EV_WRITE); + bufferevent_free(retval); + } + if (relay_fd != -1) + redsocks_close(relay_fd); + return NULL; } + int red_socket_geterrno(struct bufferevent *buffev) { - int error; - int pseudo_errno; - socklen_t optlen = sizeof(pseudo_errno); + int error; + int pseudo_errno; + socklen_t optlen = sizeof(pseudo_errno); + int fd = bufferevent_getfd(buffev); + + error = getsockopt(fd, SOL_SOCKET, SO_ERROR, &pseudo_errno, &optlen); + if (error) { + log_errno(LOG_ERR, "getsockopt(fd=%d)", fd); + return -1; + } + return pseudo_errno; +} - assert(EVENT_FD(&buffev->ev_read) == EVENT_FD(&buffev->ev_write)); +int red_is_socket_connected_ok(struct bufferevent *buffev) +{ + int pseudo_errno = red_socket_geterrno(buffev); + + if (pseudo_errno == -1) { + return 0; + } + else if (pseudo_errno) { + errno = pseudo_errno; + log_errno(LOG_NOTICE, "connect"); + return 0; + } + else { + return 1; + } +} - error = getsockopt(EVENT_FD(&buffev->ev_read), SOL_SOCKET, SO_ERROR, &pseudo_errno, &optlen); - if (error) { - log_errno(LOG_ERR, "getsockopt"); - return -1; - } - return pseudo_errno; +char *red_inet_ntop(const struct sockaddr_storage* sa, char* buffer, size_t buffer_size) +{ + const char *retval = 0; + size_t len = 0; + uint16_t port; + const char placeholder[] = "???:???"; + + assert(buffer_size >= RED_INET_ADDRSTRLEN); + + memset(buffer, 0, buffer_size); + if (sa->ss_family == AF_INET) { + retval = inet_ntop(AF_INET, &((const struct sockaddr_in *)sa)->sin_addr, buffer, buffer_size); + port = ((struct sockaddr_in*)sa)->sin_port; + } + else if (sa->ss_family == AF_INET6) { + buffer[0] = '['; + retval = inet_ntop(AF_INET6, &((const struct sockaddr_in6*)sa)->sin6_addr, buffer+1, buffer_size-1); + port = ((struct sockaddr_in6*)sa)->sin6_port; + if (retval) + retval = buffer; + } + if (retval) { + assert(retval == buffer); + len = strlen(retval); + if (sa->ss_family == AF_INET6) + snprintf(buffer + len, buffer_size - len, "]:%d", ntohs(port)); + else + snprintf(buffer + len, buffer_size - len, ":%d", ntohs(port)); + } + else { + strcpy(buffer, placeholder); + } + return buffer; } -/** simple fcntl(2) wrapper, provides errno and all logging to caller - * I have to use it in event-driven code because of accept(2) (see NOTES) - * and connect(2) (see ERRORS about EINPROGRESS) +/* copy event buffer from source to destination as much as possible. + * If parameter skip is not zero, copy will start from the number of skip bytes. */ -int fcntl_nonblock(int fd) +size_t copy_evbuffer(struct bufferevent * dst, struct bufferevent * src, size_t skip) { - int error; - int flags; + int n, i; + size_t written = 0; + struct evbuffer_iovec *v; + struct evbuffer_iovec quick_v[5];/* a vector with 5 elements is usually enough */ + struct evbuffer * evbinput = bufferevent_get_input(src); + size_t maxlen = get_write_hwm(dst) - evbuffer_get_length(bufferevent_get_output(dst)); + maxlen = evbuffer_get_length(evbinput) - skip > maxlen ? maxlen: evbuffer_get_length(evbinput)-skip; + + n = evbuffer_peek(evbinput, maxlen+skip, NULL, NULL, 0); + if (n > sizeof(quick_v)/sizeof(struct evbuffer_iovec)) + v = malloc(sizeof(struct evbuffer_iovec)*n); + else + v = &quick_v[0]; + n = evbuffer_peek(evbinput, maxlen+skip, NULL, v, n); + for (i=0; i= len) + { + skip -= len; + continue; + } + else + { + len -= skip; + } + if (written + len > maxlen) + len = maxlen - written; + if (bufferevent_write(dst, v[i].iov_base+skip, len)) + break; + skip = 0; + /* We keep track of the bytes written separately; if we don't, + * we may write more than we need if the last chunk puts + * us over the limit. */ + written += len; + } + if (v != &quick_v[0]) + free(v); + return written; +} - flags = fcntl(fd, F_GETFL); - if (flags == -1) - return -1; +size_t get_write_hwm(struct bufferevent *bufev) +{ +#if LIBEVENT_VERSION_NUMBER >= 0x02010100 + size_t high; + bufferevent_getwatermark(bufev, EV_WRITE, NULL, &high); + return high; +#else + return bufev->wm_write.high; +#endif +} + +int make_socket_transparent(int fd) +{ + int on = 1; + int error = 0; +#ifdef SOL_IPV6 + int error_6 = 0; +#endif - error = fcntl(fd, F_SETFL, flags | O_NONBLOCK); - if (error) - return -1; + error = setsockopt(fd, SOL_IP, IP_TRANSPARENT, &on, sizeof(on)); + if (error) + log_errno(LOG_DEBUG, "setsockopt(fd, SOL_IP, IP_TRANSPARENT)"); - return 0; +#ifdef SOL_IPV6 + error_6 = setsockopt(fd, SOL_IPV6, IPV6_TRANSPARENT, &on, sizeof(on)); + if (error_6) + log_errno(LOG_DEBUG, "setsockopt(fd, SOL_IPV6, IPV6_TRANSPARENT)"); + + if (error && error_6) + log_error(LOG_ERR, "Can not make socket transparent. See debug log for details."); +#endif + return error; } -int red_is_socket_connected_ok(struct bufferevent *buffev) +int apply_tcp_fastopen(int fd) +{ +#ifdef TCP_FASTOPEN +#ifdef __APPLE__ + int opt = 1; +#else + int opt = 5; +#endif + int rc = setsockopt(fd, IPPROTO_TCP, TCP_FASTOPEN, &opt, sizeof(opt)); + if (rc == -1) + log_errno(LOG_ERR, "setsockopt"); + return rc; +#else + return -1; +#endif +} + + +void replace_readcb(struct bufferevent * buffev, bufferevent_data_cb readcb) { - int pseudo_errno = red_socket_geterrno(buffev); - - if (pseudo_errno == -1) { - return 0; - } - else if (pseudo_errno) { - errno = pseudo_errno; - log_errno(LOG_NOTICE, "connect"); - return 0; - } - else { - return 1; - } -} - -char *red_inet_ntop(const struct sockaddr_in* sa, char* buffer, size_t buffer_size) -{ - const char *retval = 0; - size_t len = 0; - uint16_t port; - const char placeholder[] = "???:???"; - - assert(buffer_size >= sizeof(placeholder)); - - memset(buffer, 0, buffer_size); - if (sa->sin_family == AF_INET) { - retval = inet_ntop(AF_INET, &sa->sin_addr, buffer, buffer_size); - port = ((struct sockaddr_in*)sa)->sin_port; - } - else if (sa->sin_family == AF_INET6) { - retval = inet_ntop(AF_INET6, &((const struct sockaddr_in6*)sa)->sin6_addr, buffer, buffer_size); - port = ((struct sockaddr_in6*)sa)->sin6_port; - } - if (retval) { - assert(retval == buffer); - len = strlen(retval); - snprintf(buffer + len, buffer_size - len, ":%d", ntohs(port)); - } - else { - strcpy(buffer, placeholder); - } - return buffer; +#if LIBEVENT_VERSION_NUMBER >= 0x02010100 + bufferevent_event_cb eventcb; + bufferevent_data_cb writecb; + void * arg; + bufferevent_getcb(buffev, NULL, &writecb, &eventcb, &arg); + bufferevent_setcb(buffev, readcb, writecb, eventcb, arg); +#else + buffev->readcb = readcb; +#endif +} + +void replace_writecb(struct bufferevent * buffev, bufferevent_data_cb writecb) +{ +#if LIBEVENT_VERSION_NUMBER >= 0x02010100 + bufferevent_event_cb eventcb; + bufferevent_data_cb readcb; + void * arg; + bufferevent_getcb(buffev, &readcb, NULL, &eventcb, &arg); + bufferevent_setcb(buffev, readcb, writecb, eventcb, arg); +#else + buffev->writecb = writecb; +#endif } +void replace_eventcb(struct bufferevent * buffev, bufferevent_event_cb eventcb) +{ +#if LIBEVENT_VERSION_NUMBER >= 0x02010100 + bufferevent_data_cb readcb, writecb; + void * arg; + bufferevent_getcb(buffev, &readcb, &writecb, NULL, &arg); + bufferevent_setcb(buffev, readcb, writecb, eventcb, arg); +#else + buffev->errorcb = eventcb; +#endif +} + +int resolve_hostname(const char *hostname, int sa_family, struct sockaddr *addr) { + char addr_str[RED_INET_ADDRSTRLEN]; + struct addrinfo *ainfo, hints; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = sa_family; /* IPv4-only */ + hints.ai_socktype = SOCK_STREAM; /* I want to have one address once and ONLY once, that's why I specify socktype and protocol */ + hints.ai_protocol = IPPROTO_TCP; + hints.ai_flags = AI_ADDRCONFIG; /* I don't need IPv4 addrs without IPv4 connectivity */ + int addr_err = getaddrinfo(hostname, NULL, &hints, &ainfo); + if (addr_err == 0) { + int count, taken; + struct addrinfo *iter; + struct sockaddr *resolved_addr; + for (iter = ainfo, count = 0; iter; iter = iter->ai_next, ++count) + ; + taken = red_randui32() % count; + for (iter = ainfo; taken > 0; iter = iter->ai_next, --taken) + ; + resolved_addr = iter->ai_addr; + assert(resolved_addr->sa_family == iter->ai_family && iter->ai_family == sa_family); + if (count != 1) + log_error(LOG_WARNING, "%s resolves to %d addresses, using %s", + hostname, + count, + red_inet_ntop((const struct sockaddr_storage*)resolved_addr, addr_str, sizeof(addr_str))); + if (resolved_addr->sa_family == AF_INET) { + memcpy(&(((struct sockaddr_in*)addr)->sin_addr), + &(((struct sockaddr_in*)resolved_addr)->sin_addr), + sizeof(struct in_addr)); + } + else if (resolved_addr->sa_family == AF_INET6) { + memcpy(&(((struct sockaddr_in6*)addr)->sin6_addr), + &(((struct sockaddr_in6*)resolved_addr)->sin6_addr), + sizeof(struct in6_addr)); + } + freeaddrinfo(ainfo); + addr->sa_family = sa_family; + return 0; + } + else { + log_errno(LOG_INFO, "Unable to resolve hostname (%s): %s", + sa_family == AF_INET6 ? "IPv6": "IPv4", + hostname); + return -1; + } +} + +void set_sockaddr_port(struct sockaddr_storage * addr, uint16_t port) { + if (addr->ss_family == AF_INET) { + ((struct sockaddr_in *)addr)->sin_port = port; + } + else if (addr->ss_family == AF_INET6) { + ((struct sockaddr_in6 *)addr)->sin6_port = port; + } + else { + log_error(LOG_ERR, "Unknown address type: %d", addr->ss_family); + } +} /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ diff --git a/utils.h b/utils.h index 2297393a..5362b43e 100644 --- a/utils.h +++ b/utils.h @@ -3,7 +3,14 @@ #include #include -#include +#include +#include +#include +#include +#if defined(ENABLE_HTTPS_PROXY) +#include +#include +#endif struct sockaddr_in; @@ -50,28 +57,69 @@ struct sockaddr_in; uint32_t red_randui32(); time_t redsocks_time(time_t *t); char *redsocks_evbuffer_readline(struct evbuffer *buf); -struct bufferevent* red_connect_relay(struct sockaddr_in *addr, evbuffercb writecb, everrorcb errorcb, void *cbarg); +struct bufferevent* red_prepare_relay(const char *ifname, + int sa_family, + bufferevent_data_cb readcb, + bufferevent_data_cb writecb, + bufferevent_event_cb errorcb, + void *cbarg); +struct bufferevent* red_connect_relay(const char *ifname, + struct sockaddr_storage *addr, + bufferevent_data_cb readcb, + bufferevent_data_cb writecb, + bufferevent_event_cb errorcb, + void *cbarg, + const struct timeval *timeout_write); +#if defined(ENABLE_HTTPS_PROXY) +struct bufferevent* red_connect_relay_ssl(const char *ifname, + struct sockaddr_storage *addr, + SSL * ssl, + bufferevent_data_cb readcb, + bufferevent_data_cb writecb, + bufferevent_event_cb errorcb, + void *cbarg, + const struct timeval *timeout_write); +#endif +struct bufferevent* red_connect_relay_tfo(const char *ifname, + struct sockaddr_storage *addr, + bufferevent_data_cb readcb, + bufferevent_data_cb writecb, + bufferevent_event_cb errorcb, + void *cbarg, + const struct timeval *timeout_write, + void *data, + size_t *len); + int red_socket_geterrno(struct bufferevent *buffev); int red_is_socket_connected_ok(struct bufferevent *buffev); -int red_recv_udp_pkt(int fd, char *buf, size_t buflen, struct sockaddr_in *fromaddr, struct sockaddr_in *toaddr); +int red_recv_udp_pkt(int fd, char *buf, size_t buflen, struct sockaddr_storage *fromaddr, struct sockaddr_storage *toaddr); -int fcntl_nonblock(int fd); +size_t copy_evbuffer(struct bufferevent * dst, struct bufferevent * src, size_t skip); +size_t get_write_hwm(struct bufferevent *bufev); +int make_socket_transparent(int fd); +int apply_tcp_fastopen(int fd); +void replace_readcb(struct bufferevent * buffev, bufferevent_data_cb readcb); +void replace_writecb(struct bufferevent * buffev, bufferevent_data_cb writecb); +void replace_eventcb(struct bufferevent * buffev, bufferevent_event_cb eventcb); -#define event_fmt_str "%s|%s|%s|%s|%s|0x%x" +#define event_fmt_str "%s|%s|%s|%s|%s|%s|0x%x" #define event_fmt(what) \ - (what) & EVBUFFER_READ ? "EVBUFFER_READ" : "0", \ - (what) & EVBUFFER_WRITE ? "EVBUFFER_WRITE" : "0", \ - (what) & EVBUFFER_EOF ? "EVBUFFER_EOF" : "0", \ - (what) & EVBUFFER_ERROR ? "EVBUFFER_ERROR" : "0", \ - (what) & EVBUFFER_TIMEOUT ? "EVBUFFER_TIMEOUT" : "0", \ - (what) & ~(EVBUFFER_READ|EVBUFFER_WRITE|EVBUFFER_EOF|EVBUFFER_ERROR|EVBUFFER_TIMEOUT) + (what) & BEV_EVENT_READING ? "READING" : "0", \ + (what) & BEV_EVENT_WRITING ? "WRITING" : "0", \ + (what) & BEV_EVENT_EOF ? "EOF" : "0", \ + (what) & BEV_EVENT_ERROR ? "ERROR" : "0", \ + (what) & BEV_EVENT_TIMEOUT ? "TIMEOUT" : "0", \ + (what) & BEV_EVENT_CONNECTED ? "CONNECTED" : "0", \ + (what) & ~(BEV_EVENT_READING|BEV_EVENT_WRITING|BEV_EVENT_EOF|BEV_EVENT_ERROR|BEV_EVENT_TIMEOUT|BEV_EVENT_CONNECTED) #if INET6_ADDRSTRLEN < INET_ADDRSTRLEN # error Impossible happens: INET6_ADDRSTRLEN < INET_ADDRSTRLEN #else -# define RED_INET_ADDRSTRLEN (INET6_ADDRSTRLEN + 1 + 5 + 1) // addr + : + port + \0 +# define RED_INET_ADDRSTRLEN (1 + INET6_ADDRSTRLEN + 1 + 1 + 5 + 1) // [ + addr + ] + : + port + \0 #endif -char *red_inet_ntop(const struct sockaddr_in* sa, char* buffer, size_t buffer_size); +char *red_inet_ntop(const struct sockaddr_storage * sa, char* buffer, size_t buffer_size); +int resolve_hostname(const char *hostname, int sa_family, struct sockaddr *addr); +void set_sockaddr_port(struct sockaddr_storage * addr, uint16_t port); /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */