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:
-
- you use tor and don't want any TCP connection to leak
- you use DVB ISP and this ISP provides internet connectivity with some
- special daemon that may be also called "Internet accelerator" and this
- accelerator acts as proxy. Globax is example of such an accelerator
-
-
-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:
-
- bind9 (server)
- dig, nslookup (tools based on bind9 code)
-
-Known non-compliant resolvers are:
-
- eglibc resolver fails without any attempt to send request via TCP
- powerdns-recursor can't properly startup without UDP connectivity as it
- can't load root hints
-
-
-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
-
-
- Test OpenBSD (pf) and FreeBSD (ipfw) and write setup examples for those
- firewall types.
-
-
-
-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
+=========
+[](https://github.com/semigodking/redsocks/actions)
+[](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={,}: */