├── .gitignore ├── .travis.yml ├── Makefile ├── README.md ├── apple.c ├── apple.h ├── base.c ├── base.h ├── base64.c ├── base64.h ├── debian ├── changelog ├── compat ├── control ├── copyright ├── init.d ├── postinst ├── postrm ├── redsocks.8 ├── redsocks.conf ├── redsocks.default ├── redsocks.docs ├── redsocks.install ├── redsocks.manpages ├── rules ├── source │ └── format └── watch ├── debug.c ├── dnstc.c ├── dnstc.h ├── dnsu2t.c ├── dnsu2t.h ├── doc ├── balabit-TPROXY-README.txt ├── iptables-packet-flow-ng.png ├── iptables-packet-flow.png ├── rfc1928-socks5.txt ├── rfc1929-socks5-auth.txt ├── rfc1961-socks5-gssapi.txt ├── rfc2617-http-authentication.txt ├── rfc2817-http-connect.txt ├── rfc3089-socks-ipv6.txt ├── socks4.protocol.txt └── socks4a.protocol.txt ├── http-auth.c ├── http-auth.h ├── http-connect.c ├── http-relay.c ├── libc-compat.h ├── libevent-compat.h ├── list.h ├── log.c ├── log.h ├── main.c ├── main.h ├── md5.c ├── md5.h ├── parser.c ├── parser.h ├── redsocks.c ├── redsocks.conf.example ├── redsocks.h ├── redsocks.service ├── redudp.c ├── redudp.h ├── socks4.c ├── socks5.c ├── socks5.h ├── tdestroy.c ├── tests ├── build ├── cleanup ├── conftest.py ├── dante │ ├── Dockerfile │ ├── danted-1080.conf │ ├── danted-1081.conf │ └── danted-waitif ├── inetd │ ├── Dockerfile │ └── testing ├── login ├── prlimit-nofile.c ├── regw │ ├── Dockerfile │ └── redsocks.conf ├── run ├── squid │ ├── Dockerfile │ ├── basic.passwd │ ├── digest.passwd │ ├── squid-8.conf │ └── squid-9.conf ├── tank │ ├── Dockerfile │ └── benchmark.py ├── test_smoke.py └── web │ └── Dockerfile ├── timer.h ├── tools └── git-repack.sh ├── tsearch.c ├── tsearch.h ├── utils.c ├── utils.h └── version.h /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | config.h 3 | tags 4 | redsocks 5 | .depend 6 | /gen 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: 2 | - linux 3 | - osx 4 | 5 | addons: 6 | apt: 7 | packages: 8 | - libevent-dev 9 | 10 | language: c 11 | 12 | compiler: 13 | - gcc 14 | - clang 15 | 16 | script: 17 | - make all 18 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | -include make.conf 2 | OBJS := parser.o main.o redsocks.o apple.o tdestroy.o tsearch.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 dnsu2t.o gen/version.o 3 | ifeq ($(DBG_BUILD),1) 4 | OBJS += debug.o 5 | endif 6 | SRCS := $(OBJS:.o=.c) 7 | CONF := config.h 8 | DEPS := .depend 9 | OUT := redsocks 10 | VERSION := 0.5 11 | 12 | LIBS := -levent_core 13 | ifeq ($(DBG_BUILD),1) 14 | # -levent_extra is required only for `http` and `debug` 15 | LIBS += -levent_extra 16 | endif 17 | CFLAGS += -g -O2 18 | # _GNU_SOURCE is used to get splice(2), it also implies _BSD_SOURCE 19 | override CFLAGS += -std=c99 -D_DEFAULT_SOURCE -D_GNU_SOURCE -Wall 20 | #override CFLAGS += -std=c99 -D_XOPEN_SOURCE=600 -D_DEFAULT_SOURCE -D_GNU_SOURCE -Wall 21 | 22 | all: $(OUT) 23 | 24 | .PHONY: all clean distclean test 25 | 26 | tags: *.c *.h 27 | ctags -R 28 | 29 | $(CONF): 30 | @case `uname` in \ 31 | Linux*) \ 32 | echo "#define USE_IPTABLES" >$(CONF) \ 33 | ;; \ 34 | OpenBSD) \ 35 | echo "#define USE_PF" >$(CONF) \ 36 | ;; \ 37 | *) \ 38 | echo "Unknown system, only generic firewall code is compiled" 1>&2; \ 39 | echo "/* Unknown system, only generic firewall code is compiled */" >$(CONF) \ 40 | ;; \ 41 | esac 42 | ifeq ($(DBG_BUILD),1) 43 | echo "#define DBG_BUILD 1" >>$(CONF) 44 | endif 45 | 46 | # Dependency on .git is useful to rebuild `version.c' after commit, but it breaks non-git builds. 47 | gen/version.c: *.c *.h gen/.build 48 | rm -f $@.tmp 49 | echo '/* this file is auto-generated during build */' > $@.tmp 50 | echo '#include "../version.h"' >> $@.tmp 51 | echo 'const char* redsocks_version = ' >> $@.tmp 52 | if [ -d .git ]; then \ 53 | echo '"redsocks.git/'`git describe --tags`'"'; \ 54 | if [ `git status --porcelain | grep -v -c '^??'` != 0 ]; then \ 55 | echo '"'"-unclean-$$(date --rfc-3339=seconds | tr ' ' 'T')-$${USER}@$$(uname -n)"'"'; \ 56 | fi \ 57 | else \ 58 | echo '"redsocks/$(VERSION)"'; \ 59 | fi >> $@.tmp 60 | echo ';' >> $@.tmp 61 | mv -f $@.tmp $@ 62 | 63 | gen/.build: 64 | mkdir -p gen 65 | touch $@ 66 | 67 | base.c: $(CONF) 68 | 69 | $(DEPS): $(SRCS) 70 | gcc -MM $(SRCS) 2>/dev/null >$(DEPS) || \ 71 | ( \ 72 | for I in $(wildcard *.h); do \ 73 | echo $(I) 74 | export $${I//[-.]/_}_DEPS="`sed '/^\#[ \t]*include \?"\(.*\)".*/!d;s//\1/' $$I`"; \ 75 | done; \ 76 | echo -n >$(DEPS); \ 77 | for SRC in $(SRCS); do \ 78 | echo -n "$${SRC%.c}.o: " >>$(DEPS); \ 79 | export SRC_DEPS="`sed '/\#[ \t]*include \?"\(.*\)".*/!d;s//\1/' $$SRC | sort`"; \ 80 | while true; do \ 81 | export SRC_DEPS_OLD="$$SRC_DEPS"; \ 82 | export SRC_DEEP_DEPS=""; \ 83 | for HDR in $$SRC_DEPS; do \ 84 | eval export SRC_DEEP_DEPS="\"$$SRC_DEEP_DEPS \$$$${HDR//[-.]/_}_DEPS\""; \ 85 | done; \ 86 | export SRC_DEPS="`echo $$SRC_DEPS $$SRC_DEEP_DEPS | sed 's/ */\n/g' | sort -u`"; \ 87 | test "$$SRC_DEPS" = "$$SRC_DEPS_OLD" && break; \ 88 | done; \ 89 | echo $$SRC $$SRC_DEPS >>$(DEPS); \ 90 | done; \ 91 | ) 92 | 93 | -include $(DEPS) 94 | 95 | $(OUT): $(OBJS) 96 | $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) $(LIBS) 97 | 98 | clean: 99 | $(RM) $(OUT) $(CONF) $(OBJS) 100 | 101 | distclean: clean 102 | $(RM) tags $(DEPS) 103 | $(RM) -r gen 104 | 105 | tests/__build-tstamp__: $(OUT) tests/[a-z]* tests/[a-z]*/* 106 | cd tests && ./build 107 | touch $@ 108 | 109 | tests/prlimit-nofile: tests/prlimit-nofile.c 110 | $(CC) $(CFLAGS) -o $@ $^ 111 | 112 | test: tests/__build-tstamp__ tests/prlimit-nofile 113 | cd tests && env $(TEST_ENV) ./run 114 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # redsocks – transparent TCP-to-proxy redirector 2 | 3 | This tool allows you to redirect any TCP connection to SOCKS or HTTPS 4 | proxy using your firewall, so redirection may be system-wide or network-wide. 5 | 6 | When is redsocks useful? 7 | 8 | * you want to route part of TCP traffic via OpenSSH `DynamicForward` Socks5 9 | port using firewall policies. That was original redsocks development goal; 10 | * you use DVB ISP and this ISP provides internet connectivity with some 11 | special daemon that may be also called "Internet accelerator" and the 12 | accelerator acts as a proxy and has no "transparent proxy" feature and you 13 | need it. [Globax](http://www.globax.biz) was an example of alike accelerator, 14 | but Globax 5 has transparent proxy feature. That was the second redsocks` 15 | development goal; 16 | * you have to pass traffic through proxy due to corporate network limitation. 17 | That was never a goal for redsocks, but users have reported success with 18 | some proxy configurations. 19 | 20 | When is redsocks probably a wrong tool? 21 | 22 | * redirecting traffic to [tor](https://www.torproject.org). First, you **have** 23 | to [use tor-aware software for anonymity](https://www.torproject.org/download/download.html.en#warning). 24 | Second, [use `TransPort`](https://trac.torproject.org/projects/tor/wiki/doc/TransparentProxy) 25 | if you don't actually need anonymity. Third, question everything :-) 26 | * trying to redirect traffic of significant number of connections over single 27 | SSH connection. That's not exactly [TCP over TCP](http://sites.inka.de/bigred/devel/tcp-tcp.html), 28 | but [head-of-line blocking](https://en.wikipedia.org/wiki/Head-of-line_blocking) 29 | will still happen and performance of real-time applications (IM, interactive 30 | Web applications) may be degraded during bulk transfers; 31 | * trying to make non-transparent HTTP-proxy (not HTTPS-proxy) transparent using 32 | `http-relay` module. First, it will likely be broken as the code is hack. 33 | Second, the code is vulnerable to `CVE-2009-0801` and will unlikely be ever fixed; 34 | * making "really" transparent proxy, redsocks acts at TCP level, so three-way 35 | handshake is completed and redsocks accepts connection before connection 36 | through proxy (and _to_ proxy) is established; 37 | * trying to redirect traffic of significant number of connections in 38 | resource-constrained environment like SOHO Linux router. Throughput of single 39 | connection may be good enough like 40 Mbit/s 40 | on [TP-Link TD-W8980](https://wiki.openwrt.org/toh/tp-link/td-w8980), 41 | but amount of concurrent connections may be limiting factor as TCP buffers 42 | are still consumed; 43 | * redirecting traffic to proxy on mobile device running Android or iOS as it'll require 44 | [rooting](https://en.wikipedia.org/wiki/Rooting_(Android)) to update firewall 45 | rules. Probably, the better way is to use on-device VPN daemon to intercept 46 | traffic via [`VpnService` API for Android](https://developer.android.com/reference/android/net/VpnService.html) 47 | and [`NETunnelProvider` family of APIs for iOS](https://developer.apple.com/documentation/networkextension). 48 | That may require some code doing [TCP Reassembly](https://wiki.wireshark.org/TCP_Reassembly) 49 | like [`tun2socks`](https://github.com/ambrop72/badvpn/wiki/Tun2socks). 50 | 51 | Linux/iptables is supported. OpenBSD/pf and FreeBSD/ipfw may work with some 52 | hacks. The author has no permanent root access to machines running OpenBSD, 53 | FreeBSD and MacOSX to test and develop for these platforms. 54 | 55 | [Transocks](http://transocks.sourceforge.net/) is alike project but it has 56 | noticeable performance penality. 57 | 58 | [Transsocks_ev](http://oss.tiggerswelt.net/transocks_ev/) 59 | is alike project too, but it has no HTTPS-proxy support 60 | and does not support authentication. 61 | 62 | Several Android apps also use redsocks under-the-hood: 63 | [ProxyDroid](https://github.com/madeye/proxydroid) 64 | [](https://market.android.com/details?id=org.proxydroid) and 65 | [sshtunnel](https://code.google.com/archive/p/sshtunnel/) 66 | [](https://market.android.com/details?id=org.sshtunnel). 67 | And that's over 1'500'000 downloads! Wow! 68 | 69 | ## Features 70 | 71 | Redirect any TCP connection to Socks4, Socks5 or HTTPS (HTTP/CONNECT) 72 | proxy server. 73 | 74 | Login/password authentication is supported for Socks5/HTTPS connections. 75 | Socks4 supports only username, password is ignored. for HTTPS, currently 76 | only Basic and Digest scheme is supported. 77 | 78 | Redirect UDP packets via Socks5 proxy server. NB: UDP still goes via UDP, so 79 | you can't relay UDP via OpenSSH. 80 | 81 | Handle DNS/UDP queries sending "truncated reply" as an answer or making them 82 | DNS/TCP queries to some recursive resolver. 83 | 84 | Redirect any HTTP connection to proxy that does not support transparent 85 | proxying (e.g. old SQUID had broken `acl myport' for such connections). 86 | 87 | ### Enforcing DNS over TCP using `dnstc` 88 | 89 | DNS is running over UDP and it may be an issue in some environments as proxy 90 | servers usually don't handle UDP as a first-class citizen. Redsocks includes 91 | `dnstc` that is fake and really dumb DNS server that returns "truncated answer" 92 | to every query via UDP. RFC-compliant resolver should repeat same query via TCP 93 | in this case - so the request can be redirected using usual redsocks facilities. 94 | 95 | Known compliant resolvers are: 96 | 97 | * bind9 (server); 98 | * dig, nslookup (tools based on bind9 code). 99 | 100 | Known non-compliant resolvers are: 101 | 102 | * eglibc resolver fails without any attempt to send request via TCP; 103 | * powerdns-recursor can't properly startup without UDP connectivity as it 104 | can't load root hints. 105 | 106 | On the other hand, DNS via TCP using bind9 may be painfully slow. 107 | If your bind9 setup is really slow, you may want to try 108 | [pdnsd](http://www.phys.uu.nl/~rombouts/pdnsd.html) caching server 109 | that can run in TCP-only mode. 110 | 111 | ### Relaying DNS/UDP to DNS/TCP via `dnsu2t` 112 | 113 | The code acts as DNS server that multiplexes several UDP queries into single 114 | stream of TCP queries over keep-alive connection to upstream DNS server that 115 | should be recursive resolver. TCP connection may be handled by `redsocks` 116 | itself if firewall is configured with corresponding rules. 117 | 118 | Different resolvers have different timeouts and allow different count of 119 | in-flight connections, so you have to tune options yourself for optimal 120 | performance (with some black magic, as script testing for optimal DNS/TCP 121 | connection parameters is not written yet). 122 | 123 | There are other programs doing alike job (with, probably, different bugs) 124 | 125 | * [ttdnsd](http://www.mulliner.org/collin/ttdnsd.php) 126 | * [dns2socks](https://github.com/qiuzi/dns2socks) for Windows 127 | * [tcpdnsproxy](https://github.com/jtripper/dns-tcp-socks-proxy) 128 | 129 | ## Source 130 | 131 | Source is available at [ GitHub](https://github.com/darkk/redsocks). 132 | 133 | Issue tracker is also at GitHub, but keep in mind that the project is not 134 | actively maintained, so feature requests will unlikely be implemented within 135 | reasonable timeframe. Reproducable bugs having clean desciption will likely be 136 | fixed. Destiny of hard-to-reproduce bugs is hard to predict. 137 | 138 | New network protocols will unlikely be implemented within this source tree, but 139 | if you're seeking for censorship circumvention protocols, you may want to take 140 | a look at [redsocks2](https://github.com/semigodking/redsocks) by Zhuofei Wang 141 | AKA @semigodking who is actively maintaining the fork with GFW in mind. 142 | 143 | ## License 144 | 145 | All source code is licensed under Apache 2.0 license. 146 | You can get a copy at http://www.apache.org/licenses/LICENSE-2.0.html 147 | 148 | ## Packages 149 | 150 | * Archlinux: https://aur.archlinux.org/packages/redsocks-git 151 | * Debian: http://packages.debian.org/search?searchon=names&keywords=redsocks 152 | * Gentoo (zugaina overlay): http://gpo.zugaina.org/net-proxy/redsocks 153 | * Gentoo: https://packages.gentoo.org/packages/net-proxy/redsocks 154 | * Ubuntu: http://packages.ubuntu.com/search?searchon=names&keywords=redsocks 155 | 156 | ## Compilation 157 | 158 | [libevent-2.0.x](http://libevent.org/) is required. 159 | 160 | gcc and clang are supported right now, other compilers can be used 161 | but may require some code changes. 162 | 163 | Compilation is as easy as running `make`, there is no `./configure` magic. 164 | 165 | GNU Make works, other implementations of make were not tested. 166 | 167 | ## Running 168 | 169 | Program has following command-line options: 170 | 171 | * `-c` sets proper path to config file ("./redsocks.conf" is default one) 172 | * `-t` tests config file syntax 173 | * `-p` set a file to write the getpid() into 174 | 175 | Following signals are understood: 176 | SIGUSR1 dumps list of connected clients to log, 177 | SIGTERM and SIGINT terminates daemon, all active connections are closed. 178 | 179 | You can see configuration file example in [redsocks.conf.example](https://github.com/darkk/redsocks/blob/master/redsocks.conf.example). 180 | 181 | ### iptables example 182 | 183 | You have to build iptables with connection tracking and REDIRECT target. 184 | 185 | ``` 186 | # Create new chain 187 | root# iptables -t nat -N REDSOCKS 188 | 189 | # Ignore LANs and some other reserved addresses. 190 | # See http://en.wikipedia.org/wiki/Reserved_IP_addresses#Reserved_IPv4_addresses 191 | # and http://tools.ietf.org/html/rfc5735 for full list of reserved networks. 192 | root# iptables -t nat -A REDSOCKS -d 0.0.0.0/8 -j RETURN 193 | root# iptables -t nat -A REDSOCKS -d 10.0.0.0/8 -j RETURN 194 | root# iptables -t nat -A REDSOCKS -d 100.64.0.0/10 -j RETURN 195 | root# iptables -t nat -A REDSOCKS -d 127.0.0.0/8 -j RETURN 196 | root# iptables -t nat -A REDSOCKS -d 169.254.0.0/16 -j RETURN 197 | root# iptables -t nat -A REDSOCKS -d 172.16.0.0/12 -j RETURN 198 | root# iptables -t nat -A REDSOCKS -d 192.168.0.0/16 -j RETURN 199 | root# iptables -t nat -A REDSOCKS -d 198.18.0.0/15 -j RETURN 200 | root# iptables -t nat -A REDSOCKS -d 224.0.0.0/4 -j RETURN 201 | root# iptables -t nat -A REDSOCKS -d 240.0.0.0/4 -j RETURN 202 | 203 | # Anything else should be redirected to port 12345 204 | root# iptables -t nat -A REDSOCKS -p tcp -j REDIRECT --to-ports 12345 205 | 206 | # Any tcp connection made by `luser' should be redirected. 207 | root# iptables -t nat -A OUTPUT -p tcp -m owner --uid-owner luser -j REDSOCKS 208 | 209 | # You can also control that in more precise way using `gid-owner` from 210 | # iptables. 211 | root# groupadd socksified 212 | root# usermod --append --groups socksified luser 213 | root# iptables -t nat -A OUTPUT -p tcp -m owner --gid-owner socksified -j REDSOCKS 214 | 215 | # Now you can launch your specific application with GID `socksified` and it 216 | # will be... socksified. See following commands (numbers may vary). 217 | # Note: you may have to relogin to apply `usermod` changes. 218 | luser$ id 219 | uid=1000(luser) gid=1000(luser) groups=1000(luser),1001(socksified) 220 | luser$ sg socksified -c id 221 | uid=1000(luser) gid=1001(socksified) groups=1000(luser),1001(socksified) 222 | luser$ sg socksified -c "firefox" 223 | 224 | # If you want to configure socksifying router, you should look at 225 | # doc/iptables-packet-flow.png, doc/iptables-packet-flow-ng.png and 226 | # https://en.wikipedia.org/wiki/File:Netfilter-packet-flow.svg 227 | # Note, you should have proper `local_ip' value to get external packets with 228 | # redsocks, default 127.0.0.1 will not go. See iptables(8) manpage regarding 229 | # REDIRECT target for details. 230 | # Depending on your network configuration iptables conf. may be as easy as: 231 | root# iptables -t nat -A PREROUTING --in-interface eth_int -p tcp -j REDSOCKS 232 | ``` 233 | 234 | ### Note about GID-based redirection 235 | 236 | Keep in mind, that changed GID affects filesystem permissions, so if your 237 | application creates some files, the files will be created with luser:socksified 238 | owner/group. So, if you're not the only user in the group `socksified` and your 239 | umask allows to create group-readable files and your directory permissions, and 240 | so on, blah-blah, etc. THEN you may expose your files to another user. 241 | Ok, you have been warned. 242 | 243 | ## Homepage 244 | 245 | http://darkk.net.ru/redsocks/ 246 | 247 | Mailing list: [redsocks@librelist.com](mailto:redsocks@librelist.com). 248 | 249 | Mailing list also has [archives](http://librelist.com/browser/redsocks/). 250 | 251 | ## Author 252 | 253 | This program was written by Leonid Evdokimov 254 | -------------------------------------------------------------------------------- /apple.c: -------------------------------------------------------------------------------- 1 | #ifdef __APPLE__ 2 | #include 3 | #include 4 | 5 | int pipe2(int pipefd[2], int flags) 6 | { 7 | return 0; 8 | } 9 | ssize_t splice(int fd_in, off_t *off_in, int fd_out, off_t *off_out, size_t len, unsigned int flags) 10 | { 11 | return 0; 12 | } 13 | 14 | #endif -------------------------------------------------------------------------------- /apple.h: -------------------------------------------------------------------------------- 1 | #ifndef APPLE_H_SAT_DEC_17_46_12_07_2019 2 | #define APPLE_H_SAT_DEC_17_46_12_07_2019 3 | 4 | #ifdef __APPLE__ 5 | #define USE_OWNTIMER 1 6 | #endif 7 | 8 | #ifdef USE_OWNTIMER 9 | #include "timer.h" 10 | #else 11 | #define Timersub timersub 12 | #define Timeradd timeradd 13 | #define Timercmp timercmp 14 | #endif 15 | 16 | #ifndef CMSG_LEN 17 | #define CMSG_LEN(l) (__DARWIN_ALIGN32(sizeof(struct cmsghdr)) + (l)) 18 | #endif 19 | 20 | #ifndef MSG_FASTOPEN 21 | #define MSG_FASTOPEN 0 22 | #endif 23 | 24 | #ifndef SOL_IP 25 | #define SOL_IP IPPROTO_IP 26 | #endif 27 | 28 | #ifndef INADDR_LOOPBACK 29 | #define INADDR_LOOPBACK ((unsigned long) 0x7f000001) 30 | #endif 31 | 32 | /* 33 | * Even though Mac OS X does not support the splice implementation of 34 | * Linux, define the associated flags to avoid undeclared identifier 35 | * errors. 36 | */ 37 | #ifndef SPLICE_F_MOVE 38 | #define SPLICE_F_MOVE 0x01 39 | #endif 40 | #ifndef SPLICE_F_NONBLOCK 41 | #define SPLICE_F_NONBLOCK 0x02 42 | #endif 43 | #ifndef SPLICE_F_MORE 44 | #define SPLICE_F_MORE 0x04 45 | #endif 46 | #ifndef SPLICE_F_GIFT 47 | #define SPLICE_F_GIFT 0x08 48 | #endif 49 | 50 | #endif -------------------------------------------------------------------------------- /base.h: -------------------------------------------------------------------------------- 1 | #ifndef BASE_H_SUN_JUN__3_20_15_57_2007 2 | #define BASE_H_SUN_JUN__3_20_15_57_2007 3 | 4 | int getdestaddr(int fd, const struct sockaddr_in *client, const struct sockaddr_in *bindaddr, struct sockaddr_in *destaddr); 5 | int apply_tcp_keepalive(int fd); 6 | 7 | uint32_t redsocks_conn_max(); 8 | uint32_t connpres_idle_timeout(); 9 | uint32_t max_accept_backoff_ms(); 10 | 11 | /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ 12 | /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ 13 | #endif /* BASE_H_SUN_JUN__3_20_15_57_2007 */ 14 | -------------------------------------------------------------------------------- /base64.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2006 Ryan Martell. (rdm4@martellventures.com) 3 | * 4 | * This file is part of FFmpeg. 5 | * 6 | * FFmpeg is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 2.1 of the License, or (at your option) any later version. 10 | * 11 | * FFmpeg is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with FFmpeg; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 | */ 20 | 21 | /** 22 | * @file 23 | * @brief Base64 encode/decode 24 | * @author Ryan Martell (with lots of Michael) 25 | */ 26 | 27 | #include 28 | #include "utils.h" 29 | #include "base64.h" 30 | 31 | /* ---------------- private code */ 32 | static const uint8_t map2[] = 33 | { 34 | 0x3e, 0xff, 0xff, 0xff, 0x3f, 0x34, 0x35, 0x36, 35 | 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0xff, 36 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, 37 | 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 38 | 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 39 | 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 40 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1a, 0x1b, 41 | 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 42 | 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 43 | 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33 44 | }; 45 | 46 | int base64_decode(uint8_t *out, const char *in, int out_size) 47 | { 48 | int i, v; 49 | uint8_t *dst = out; 50 | 51 | v = 0; 52 | for (i = 0; in[i] && in[i] != '='; i++) { 53 | unsigned int index= in[i]-43; 54 | if (index>=SIZEOF_ARRAY(map2) || map2[index] == 0xff) 55 | return -1; 56 | v = (v << 6) + map2[index]; 57 | if (i & 3) { 58 | if (dst - out < out_size) { 59 | *dst++ = v >> (6 - 2 * (i & 3)); 60 | } 61 | } 62 | } 63 | 64 | return dst - out; 65 | } 66 | 67 | /***************************************************************************** 68 | * b64_encode: Stolen from VLC's http.c. 69 | * Simplified by Michael. 70 | * Fixed edge cases and made it work from data (vs. strings) by Ryan. 71 | *****************************************************************************/ 72 | 73 | char *base64_encode(char *out, int out_size, const uint8_t *in, int in_size) 74 | { 75 | static const char b64[] = 76 | "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 77 | char *ret, *dst; 78 | unsigned i_bits = 0; 79 | int i_shift = 0; 80 | int bytes_remaining = in_size; 81 | 82 | if (in_size >= UINT_MAX / 4 || 83 | out_size < BASE64_SIZE(in_size)) 84 | return NULL; 85 | ret = dst = out; 86 | while (bytes_remaining) { 87 | i_bits = (i_bits << 8) + *in++; 88 | bytes_remaining--; 89 | i_shift += 8; 90 | 91 | do { 92 | *dst++ = b64[(i_bits << 6 >> i_shift) & 0x3f]; 93 | i_shift -= 6; 94 | } while (i_shift > 6 || (bytes_remaining == 0 && i_shift > 0)); 95 | } 96 | while ((dst - ret) & 3) 97 | *dst++ = '='; 98 | *dst = '\0'; 99 | 100 | return ret; 101 | } 102 | -------------------------------------------------------------------------------- /base64.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2006 Ryan Martell. (rdm4@martellventures.com) 3 | * 4 | * This file is part of FFmpeg. 5 | * 6 | * FFmpeg is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 2.1 of the License, or (at your option) any later version. 10 | * 11 | * FFmpeg is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with FFmpeg; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 | */ 20 | 21 | #ifndef BASE64_H 22 | #define BASE64_H 23 | 24 | #include 25 | 26 | /** 27 | * Decode a base64-encoded string. 28 | * 29 | * @param out buffer for decoded data 30 | * @param in null-terminated input string 31 | * @param out_size size in bytes of the out buffer, must be at 32 | * least 3/4 of the length of in 33 | * @return number of bytes written, or a negative value in case of 34 | * invalid input 35 | */ 36 | int base64_decode(uint8_t *out, const char *in, int out_size); 37 | 38 | /** 39 | * Encode data to base64 and null-terminate. 40 | * 41 | * @param out buffer for encoded data 42 | * @param out_size size in bytes of the output buffer, must be at 43 | * least BASE64_SIZE(in_size) 44 | * @param in_size size in bytes of the 'in' buffer 45 | * @return 'out' or NULL in case of error 46 | */ 47 | char *base64_encode(char *out, int out_size, const uint8_t *in, int in_size); 48 | 49 | /** 50 | * Calculate the output size needed to base64-encode x bytes. 51 | */ 52 | #define BASE64_SIZE(x) (((x)+2) / 3 * 4 + 1) 53 | 54 | #endif /* BASE64_H */ 55 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | redsocks (0.2-1) unstable; urgency=low 2 | 3 | * Initial release (Closes: #649309) 4 | 5 | -- Apollon Oikonomopoulos Mon, 14 Nov 2011 14:42:55 +0200 6 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 8 2 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: redsocks 2 | Section: net 3 | Priority: extra 4 | Maintainer: Apollon Oikonomopoulos 5 | Build-Depends: debhelper (>= 8.0.0), libevent-dev 6 | Standards-Version: 3.9.2 7 | Homepage: http://darkk.net.ru/redsocks/ 8 | #Vcs-Git: git://git.debian.org/collab-maint/redsocks.git 9 | #Vcs-Browser: http://git.debian.org/?p=collab-maint/redsocks.git;a=summary 10 | 11 | Package: redsocks 12 | Architecture: any 13 | Depends: ${shlibs:Depends}, ${misc:Depends}, adduser 14 | Description: Redirect any TCP connection to a SOCKS or HTTPS proxy server 15 | Redsocks is a daemon running on the local system, that will transparently 16 | tunnel any TCP connection via a remote SOCKS4, SOCKS5 or HTTP proxy server. It 17 | uses the system firewall's redirection facility to intercept TCP connections, 18 | thus the redirection is system-wide, with fine-grained control, and does 19 | not depend on LD_PRELOAD libraries. 20 | . 21 | Redsocks supports tunneling TCP connections and UDP packets. It has 22 | authentication support for both, SOCKS and HTTP proxies. 23 | . 24 | Also included is a small DNS server returning answers with the "truncated" flag 25 | set for any UDP query, forcing the resolver to use TCP. 26 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Format: http://dep.debian.net/deps/dep5 2 | Upstream-Name: redsocks 3 | Source: http://darkk.net.ru/redsocks/ 4 | 5 | Files: * 6 | Copyright: 2007-2018 Leonid Evdokimov 7 | License: Apache 2.0 8 | Licensed under the Apache License, Version 2.0 (the "License"); 9 | you may not use this file except in compliance with the License. 10 | You may obtain a copy of the License at 11 | . 12 | http://www.apache.org/licenses/LICENSE-2.0 13 | . 14 | Unless required by applicable law or agreed to in writing, software 15 | distributed under the License is distributed on an "AS IS" BASIS, 16 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | See the License for the specific language governing permissions and 18 | limitations under the License. 19 | . 20 | On Debian systems, the complete text of the Apache License 2.0 can 21 | be found in "/usr/share/common-licenses/Apache-2.0" 22 | 23 | Files: md5.* 24 | Copyright: 1999, 2002 Aladdin Enterprises 25 | License: Zlib 26 | This software is provided 'as-is', without any express or implied 27 | warranty. In no event will the authors be held liable for any damages 28 | arising from the use of this software. 29 | . 30 | Permission is granted to anyone to use this software for any purpose, 31 | including commercial applications, and to alter it and redistribute it 32 | freely, subject to the following restrictions: 33 | . 34 | 1. The origin of this software must not be misrepresented; you must not 35 | claim that you wrote the original software. If you use this software 36 | in a product, an acknowledgment in the product documentation would be 37 | appreciated but is not required. 38 | 2. Altered source versions must be plainly marked as such, and must not be 39 | misrepresented as being the original software. 40 | 3. This notice may not be removed or altered from any source distribution. 41 | 42 | Files: base64.* 43 | Copyright: 2006 Ryan Martell 44 | License: LGPL-2.1+ 45 | FFmpeg is free software; you can redistribute it and/or 46 | modify it under the terms of the GNU Lesser General Public 47 | License as published by the Free Software Foundation; either 48 | version 2.1 of the License, or (at your option) any later version. 49 | . 50 | FFmpeg is distributed in the hope that it will be useful, 51 | but WITHOUT ANY WARRANTY; without even the implied warranty of 52 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 53 | Lesser General Public License for more details. 54 | . 55 | You should have received a copy of the GNU Lesser General Public 56 | License along with FFmpeg; if not, write to the Free Software 57 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 58 | . 59 | On Debian systems, the complete text of the GNU Lesser General Public 60 | License can be found in "/usr/share/common-licenses/LGPL". 61 | 62 | Files: debian/* 63 | Copyright: 2011 Apollon Oikonomopoulos 64 | License: 65 | Copying and distribution of this package, with or without 66 | modification, are permitted in any medium without royalty 67 | provided the copyright notice and this notice are 68 | preserved. 69 | -------------------------------------------------------------------------------- /debian/init.d: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | ### BEGIN INIT INFO 3 | # Provides: redsocks 4 | # Required-Start: $network $local_fs $remote_fs 5 | # Required-Stop: $remote_fs 6 | # Default-Start: 2 3 4 5 7 | # Default-Stop: 0 1 6 8 | # Short-Description: redsocks TCP connection-to-SOCKS redirector 9 | # Description: 10 | # <...> 11 | # <...> 12 | ### END INIT INFO 13 | 14 | # Author: Apollon Oikonomopoulos 15 | 16 | # PATH should only include /usr/* if it runs after the mountnfs.sh script 17 | PATH=/sbin:/usr/sbin:/bin:/usr/bin 18 | DESC=redsocks # Introduce a short description here 19 | NAME=redsocks # Introduce the short server's name here 20 | DAEMON=/usr/sbin/redsocks # Introduce the server's location here 21 | DAEMON_ARGS="" # Arguments to run the daemon with 22 | PIDFILE=/var/run/$NAME/$NAME.pid 23 | SCRIPTNAME=/etc/init.d/$NAME 24 | 25 | # Exit if the package is not installed 26 | [ -x $DAEMON ] || exit 0 27 | 28 | # Read configuration variable file if it is present 29 | [ -r /etc/default/$NAME ] && . /etc/default/$NAME 30 | 31 | [ "$START" = "yes" ] || exit 0 32 | 33 | # Load the VERBOSE setting and other rcS variables 34 | . /lib/init/vars.sh 35 | 36 | # Define LSB log_* functions. 37 | # Depend on lsb-base (>= 3.0-6) to ensure that this file is present. 38 | . /lib/lsb/init-functions 39 | 40 | # 41 | # Function that starts the daemon/service 42 | # 43 | do_start() 44 | { 45 | # Take care of pidfile permissions 46 | USER=$(egrep '\buser\s*=' $CONFFILE | sed -r 's/.*\buser\s+=\s*(.*);/\1/') 47 | mkdir /var/run/$NAME 2>/dev/null || true 48 | chown "$USER" /var/run/$NAME 49 | 50 | # Return 51 | # 0 if daemon has been started 52 | # 1 if daemon was already running 53 | # 2 if daemon could not be started 54 | start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \ 55 | || return 1 56 | start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- \ 57 | -c "$CONFFILE" -p $PIDFILE \ 58 | || return 2 59 | # Add code here, if necessary, that waits for the process to be ready 60 | # to handle requests from services started subsequently which depend 61 | # on this one. As a last resort, sleep for some time. 62 | } 63 | 64 | # 65 | # Function that stops the daemon/service 66 | # 67 | do_stop() 68 | { 69 | # Return 70 | # 0 if daemon has been stopped 71 | # 1 if daemon was already stopped 72 | # 2 if daemon could not be stopped 73 | # other if a failure occurred 74 | start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME 75 | RETVAL="$?" 76 | [ "$RETVAL" = 2 ] && return 2 77 | # Wait for children to finish too if this is a daemon that forks 78 | # and if the daemon is only ever run from this initscript. 79 | # If the above conditions are not satisfied then add some other code 80 | # that waits for the process to drop all resources that could be 81 | # needed by services started subsequently. A last resort is to 82 | # sleep for some time. 83 | start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON 84 | [ "$?" = 2 ] && return 2 85 | # Many daemons don't delete their pidfiles when they exit. 86 | rm -f $PIDFILE 87 | return "$RETVAL" 88 | } 89 | 90 | 91 | case "$1" in 92 | start) 93 | [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC " "$NAME" 94 | do_start 95 | case "$?" in 96 | 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; 97 | 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; 98 | esac 99 | ;; 100 | stop) 101 | [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME" 102 | do_stop 103 | case "$?" in 104 | 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; 105 | 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; 106 | esac 107 | ;; 108 | status) 109 | status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $? 110 | ;; 111 | restart|force-reload) 112 | log_daemon_msg "Restarting $DESC" "$NAME" 113 | do_stop 114 | case "$?" in 115 | 0|1) 116 | do_start 117 | case "$?" in 118 | 0) log_end_msg 0 ;; 119 | 1) log_end_msg 1 ;; # Old process is still running 120 | *) log_end_msg 1 ;; # Failed to start 121 | esac 122 | ;; 123 | *) 124 | # Failed to stop 125 | log_end_msg 1 126 | ;; 127 | esac 128 | ;; 129 | *) 130 | echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2 131 | exit 3 132 | ;; 133 | esac 134 | 135 | : 136 | -------------------------------------------------------------------------------- /debian/postinst: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # postinst script for redsocks 3 | # 4 | # see: dh_installdeb(1) 5 | 6 | set -e 7 | 8 | # summary of how this script can be called: 9 | # * `configure' 10 | # * `abort-upgrade' 11 | # * `abort-remove' `in-favour' 12 | # 13 | # * `abort-remove' 14 | # * `abort-deconfigure' `in-favour' 15 | # `removing' 16 | # 17 | # for details, see http://www.debian.org/doc/debian-policy/ or 18 | # the debian-policy package 19 | 20 | 21 | case "$1" in 22 | configure) 23 | adduser --system --disabled-password --disabled-login --home /var/run/redsocks \ 24 | --no-create-home --quiet --group redsocks 25 | ;; 26 | 27 | abort-upgrade|abort-remove|abort-deconfigure) 28 | ;; 29 | 30 | *) 31 | echo "postinst called with unknown argument \`$1'" >&2 32 | exit 1 33 | ;; 34 | esac 35 | 36 | # dh_installdeb will replace this with shell code automatically 37 | # generated by other debhelper scripts. 38 | 39 | #DEBHELPER# 40 | 41 | exit 0 42 | -------------------------------------------------------------------------------- /debian/postrm: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # postrm script for redsocks 3 | # 4 | # see: dh_installdeb(1) 5 | 6 | set -e 7 | 8 | # summary of how this script can be called: 9 | # * `remove' 10 | # * `purge' 11 | # * `upgrade' 12 | # * `failed-upgrade' 13 | # * `abort-install' 14 | # * `abort-install' 15 | # * `abort-upgrade' 16 | # * `disappear' 17 | # 18 | # for details, see http://www.debian.org/doc/debian-policy/ or 19 | # the debian-policy package 20 | 21 | 22 | case "$1" in 23 | purge) 24 | deluser --system redsocks || true 25 | delgroup --system redsocks || true 26 | ;; 27 | remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear) 28 | ;; 29 | 30 | *) 31 | echo "postrm called with unknown argument \`$1'" >&2 32 | exit 1 33 | ;; 34 | esac 35 | 36 | # dh_installdeb will replace this with shell code automatically 37 | # generated by other debhelper scripts. 38 | 39 | #DEBHELPER# 40 | 41 | exit 0 42 | -------------------------------------------------------------------------------- /debian/redsocks.8: -------------------------------------------------------------------------------- 1 | .\" Hey, EMACS: -*- nroff -*- 2 | .\" First parameter, NAME, should be all caps 3 | .\" Second parameter, SECTION, should be 1-8, maybe w/ subsection 4 | .\" other parameters are allowed: see man(7), man(1) 5 | .TH REDSOCKS 8 "November 14, 2011" 6 | .\" Please adjust this date whenever revising the manpage. 7 | .\" 8 | .\" Some roff macros, for reference: 9 | .\" .nh disable hyphenation 10 | .\" .hy enable hyphenation 11 | .\" .ad l left justify 12 | .\" .ad b justify to both left and right margins 13 | .\" .nf disable filling 14 | .\" .fi enable filling 15 | .\" .br insert line break 16 | .\" .sp insert n+1 empty lines 17 | .\" for manpage-specific macros, see man(7) 18 | .SH NAME 19 | redsocks \- rediect any TCP connection to a SOCKS or HTTP proxy 20 | .SH SYNOPSIS 21 | .B redsocks 22 | .RI [ options ] 23 | .SH DESCRIPTION 24 | \fBredsocks\fP is a daemon running on the local system, that will transparently 25 | tunnel any TCP connection via a remote SOCKS4, SOCKS5 or HTTP proxy server. It 26 | uses the system firewall's redirection facility to intercept TCP connections, 27 | thus the redirection is system-wide, with fine-grained control, and does 28 | not depend on LD_PRELOAD libraries. 29 | .PP 30 | Redsocks supports tunneling TCP connections and UDP packets. It has 31 | authentication support for both, SOCKS and HTTP proxies. 32 | .PP 33 | Also included is a small DNS server returning answers with the "truncated" flag 34 | set for any UDP query, forcing the resolver to use TCP. 35 | .SH OPTIONS 36 | .TP 37 | .B \-p pidfile 38 | Write the process ID to \fIpidfile\fP. 39 | .TP 40 | .B \-c config 41 | Use \fIconfig\fP as configuration file. 42 | .TP 43 | .B \-t 44 | Test configuration file syntax. 45 | .SH SEE ALSO 46 | .BR iptables (8), 47 | /etc/redsocks.conf 48 | .br 49 | .SH AUTHOR 50 | redsocks was written by Leonid Evdokimov 51 | .PP 52 | This manual page was written by Apollon Oikonomopoulos , 53 | for the Debian project (and may be used by others). 54 | -------------------------------------------------------------------------------- /debian/redsocks.conf: -------------------------------------------------------------------------------- 1 | base { 2 | // debug: connection progress & client list on SIGUSR1 3 | log_debug = off; 4 | 5 | // info: start and end of client session 6 | log_info = on; 7 | 8 | /* possible `log' values are: 9 | * stderr 10 | * "file:/path/to/file" 11 | * syslog:FACILITY facility is any of "daemon", "local0"..."local7" 12 | */ 13 | log = "syslog:daemon"; 14 | 15 | // detach from console 16 | daemon = on; 17 | 18 | /* Change uid, gid and root directory, these options require root 19 | * privilegies on startup. 20 | * Note, your chroot may requre /etc/localtime if you write log to syslog. 21 | * Log is opened before chroot & uid changing. 22 | */ 23 | user = redsocks; 24 | group = redsocks; 25 | // chroot = "/var/chroot"; 26 | 27 | /* possible `redirector' values are: 28 | * iptables - for Linux 29 | * ipf - for FreeBSD 30 | * pf - for OpenBSD 31 | * generic - some generic redirector that MAY work 32 | */ 33 | redirector = iptables; 34 | } 35 | 36 | redsocks { 37 | /* `local_ip' defaults to 127.0.0.1 for security reasons, 38 | * use 0.0.0.0 if you want to listen on every interface. 39 | * `local_*' are used as port to redirect to. 40 | */ 41 | local_ip = 127.0.0.1; 42 | local_port = 12345; 43 | 44 | // `ip' and `port' are IP and tcp-port of proxy-server 45 | // You can also use hostname instead of IP, only one (random) 46 | // address of multihomed host will be used. 47 | ip = 127.0.0.1; 48 | port = 1080; 49 | 50 | 51 | // known types: socks4, socks5, http-connect, http-relay 52 | type = socks5; 53 | 54 | // login = "foobar"; 55 | // password = "baz"; 56 | } 57 | 58 | redudp { 59 | // `local_ip' should not be 0.0.0.0 as it's also used for outgoing 60 | // packets that are sent as replies - and it should be fixed 61 | // if we want NAT to work properly. 62 | local_ip = 127.0.0.1; 63 | local_port = 10053; 64 | 65 | // `ip' and `port' of socks5 proxy server. 66 | ip = 10.0.0.1; 67 | port = 1080; 68 | login = username; 69 | password = pazzw0rd; 70 | 71 | // kernel does not give us this information, so we have to duplicate it 72 | // in both iptables rules and configuration file. By the way, you can 73 | // set `local_ip' to 127.45.67.89 if you need more than 65535 ports to 74 | // forward ;-) 75 | // This limitation may be relaxed in future versions using contrack-tools. 76 | dest_ip = 8.8.8.8; 77 | dest_port = 53; 78 | 79 | udp_timeout = 30; 80 | udp_timeout_stream = 180; 81 | } 82 | 83 | dnstc { 84 | // fake and really dumb DNS server that returns "truncated answer" to 85 | // every query via UDP, RFC-compliant resolver should repeat same query 86 | // via TCP in this case. 87 | local_ip = 127.0.0.1; 88 | local_port = 5300; 89 | } 90 | 91 | // you can add more `redsocks' and `redudp' sections if you need. 92 | -------------------------------------------------------------------------------- /debian/redsocks.default: -------------------------------------------------------------------------------- 1 | # Defaults for redsocks initscript 2 | # sourced by /etc/init.d/redsocks 3 | # installed at /etc/default/redsocks by the maintainer scripts 4 | 5 | # 6 | # This is a POSIX shell fragment 7 | # 8 | 9 | # Enable during startup? 10 | START=no 11 | 12 | # Configuration file 13 | CONFFILE="/etc/redsocks.conf" 14 | -------------------------------------------------------------------------------- /debian/redsocks.docs: -------------------------------------------------------------------------------- 1 | README 2 | README.html 3 | -------------------------------------------------------------------------------- /debian/redsocks.install: -------------------------------------------------------------------------------- 1 | redsocks /usr/sbin 2 | debian/redsocks.conf /etc 3 | -------------------------------------------------------------------------------- /debian/redsocks.manpages: -------------------------------------------------------------------------------- 1 | debian/redsocks.8 2 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | # -*- makefile -*- 3 | # Sample debian/rules that uses debhelper. 4 | # This file was originally written by Joey Hess and Craig Small. 5 | # As a special exception, when this file is copied by dh-make into a 6 | # dh-make output file, you may use that output file without restriction. 7 | # This special exception was added by Craig Small in version 0.37 of dh-make. 8 | 9 | # Uncomment this to turn on verbose mode. 10 | #export DH_VERBOSE=1 11 | 12 | %: 13 | dh $@ 14 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (quilt) 2 | -------------------------------------------------------------------------------- /debian/watch: -------------------------------------------------------------------------------- 1 | version=3 2 | 3 | # Use githubredir.debian.net for the time being 4 | http://githubredir.debian.net/github/darkk/redsocks (.*).tar.gz 5 | -------------------------------------------------------------------------------- /debug.c: -------------------------------------------------------------------------------- 1 | /* redsocks - transparent TCP-to-proxy redirector 2 | * Copyright (C) 2007-2018 Leonid Evdokimov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | #include "parser.h" 18 | #include "main.h" 19 | #include "log.h" 20 | #include "utils.h" 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | typedef struct debug_instance_t { 28 | int configured; 29 | char* http_ip; 30 | uint16_t http_port; 31 | struct evhttp* http_server; 32 | } debug_instance; 33 | 34 | static debug_instance instance = { 35 | .configured = 0, 36 | }; 37 | 38 | static parser_entry debug_entries[] = 39 | { 40 | { .key = "http_ip", .type = pt_pchar }, 41 | { .key = "http_port", .type = pt_uint16 }, 42 | { } 43 | }; 44 | 45 | static int debug_onenter(parser_section *section) 46 | { 47 | if (instance.configured) { 48 | parser_error(section->context, "only one instance of debug is valid"); 49 | return -1; 50 | } 51 | memset(&instance, 0, sizeof(instance)); 52 | for (parser_entry *entry = §ion->entries[0]; entry->key; entry++) 53 | entry->addr = 54 | (strcmp(entry->key, "http_ip") == 0) ? (void*)&instance.http_ip: 55 | (strcmp(entry->key, "http_port") == 0) ? (void*)&instance.http_port : 56 | NULL; 57 | return 0; 58 | } 59 | 60 | static int debug_onexit(parser_section *section) 61 | { 62 | instance.configured = 1; 63 | if (!instance.http_ip) 64 | instance.http_ip = strdup("localhost"); 65 | return 0; 66 | } 67 | 68 | static parser_section debug_conf_section = 69 | { 70 | .name = "debug", 71 | .entries = debug_entries, 72 | .onenter = debug_onenter, 73 | .onexit = debug_onexit, 74 | }; 75 | 76 | static void debug_pipe(struct evhttp_request *req, void *arg) 77 | { 78 | UNUSED(arg); 79 | int fds[2]; 80 | int code = (pipe(fds) == 0) ? HTTP_OK : HTTP_SERVUNAVAIL; 81 | evhttp_send_reply(req, code, NULL, NULL); 82 | } 83 | static void debug_meminfo_json(struct evhttp_request *req, void *arg) 84 | { 85 | UNUSED(arg); 86 | 87 | struct evbuffer* body = evbuffer_new(); 88 | evbuffer_add(body, "{", 1); 89 | 90 | FILE* fd = fopen("/proc/vmstat", "r"); 91 | while (!feof(fd)) { 92 | char buf[64]; 93 | size_t pages; 94 | if (fscanf(fd, "%63s %zu", buf, &pages) == 2 && strncmp(buf, "nr_", 3) == 0) { 95 | evbuffer_add_printf(body, "\"%s\": %zu, ", buf, pages); 96 | } 97 | for (int c = 0; c != EOF && c != '\n'; c = fgetc(fd)) 98 | ; 99 | } 100 | fclose(fd); 101 | 102 | size_t vmsize, rss, share, text, z, data; 103 | fd = fopen("/proc/self/statm", "r"); 104 | if (fscanf(fd, "%zu %zu %zu %zu %zu %zu %zu", &vmsize, &rss, &share, &text, &z, &data, &z) == 7) { 105 | evbuffer_add_printf(body, 106 | "\"vmsize\": %zu, \"vmrss\": %zu, \"share\": %zu, \"text\": %zu, \"data\": %zu, ", 107 | vmsize, rss, share, text, data); 108 | 109 | } 110 | fclose(fd); 111 | evbuffer_add_printf(body, "\"getpagesize\": %d}", getpagesize()); 112 | 113 | evhttp_send_reply(req, HTTP_OK, NULL, body); 114 | evbuffer_free(body); 115 | } 116 | 117 | static int debug_fini(); 118 | 119 | static int debug_init(struct event_base* evbase) 120 | { 121 | if (!instance.http_port) 122 | return 0; 123 | 124 | instance.http_server = evhttp_new(evbase); 125 | if (!instance.http_server) { 126 | log_errno(LOG_ERR, "evhttp_new()"); 127 | goto fail; 128 | } 129 | 130 | if (evhttp_bind_socket(instance.http_server, instance.http_ip, instance.http_port) != 0) { 131 | log_errno(LOG_ERR, "evhttp_bind_socket()"); 132 | goto fail; 133 | } 134 | 135 | if (evhttp_set_cb(instance.http_server, "/debug/pipe", debug_pipe, NULL) != 0) { 136 | log_errno(LOG_ERR, "evhttp_set_cb()"); 137 | goto fail; 138 | } 139 | 140 | if (evhttp_set_cb(instance.http_server, "/debug/meminfo.json", debug_meminfo_json, NULL) != 0) { 141 | log_errno(LOG_ERR, "evhttp_set_cb()"); 142 | goto fail; 143 | } 144 | 145 | return 0; 146 | 147 | fail: 148 | debug_fini(); 149 | return -1; 150 | } 151 | 152 | static int debug_fini() 153 | { 154 | if (instance.http_server) { 155 | evhttp_free(instance.http_server); 156 | instance.http_server = 0; 157 | } 158 | free(instance.http_ip); 159 | memset(&instance, 0, sizeof(instance)); 160 | return 0; 161 | } 162 | 163 | app_subsys debug_subsys = 164 | { 165 | .init = debug_init, 166 | .fini = debug_fini, 167 | .conf_section = &debug_conf_section, 168 | }; 169 | 170 | /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ 171 | /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ 172 | -------------------------------------------------------------------------------- /dnstc.c: -------------------------------------------------------------------------------- 1 | /* redsocks - transparent TCP-to-proxy redirector 2 | * Copyright (C) 2007-2018 Leonid Evdokimov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include "list.h" 28 | #include "log.h" 29 | #include "parser.h" 30 | #include "main.h" 31 | #include "redsocks.h" 32 | #include "dnstc.h" 33 | #include "utils.h" 34 | 35 | #define dnstc_log_error(prio, msg...) \ 36 | redsocks_log_write_plain(__FILE__, __LINE__, __func__, 0, &clientaddr, &self->config.bindaddr, prio, ## msg) 37 | #define dnstc_log_errno(prio, msg...) \ 38 | redsocks_log_write_plain(__FILE__, __LINE__, __func__, 1, &clientaddr, &self->config.bindaddr, prio, ## msg) 39 | 40 | static void dnstc_fini_instance(dnstc_instance *instance); 41 | static int dnstc_fini(); 42 | 43 | typedef struct dns_header_t { 44 | uint16_t id; 45 | uint8_t qr_opcode_aa_tc_rd; 46 | uint8_t ra_z_rcode; 47 | uint16_t qdcount; 48 | uint16_t ancount; 49 | uint16_t nscount; 50 | uint16_t arcount; // may be >0 for EDNS queries 51 | } PACKED dns_header; 52 | 53 | #define DNS_QR 0x80 54 | #define DNS_TC 0x02 55 | #define DNS_Z 0x70 56 | 57 | /*********************************************************************** 58 | * Logic 59 | */ 60 | static void dnstc_pkt_from_client(int fd, short what, void *_arg) 61 | { 62 | dnstc_instance *self = _arg; 63 | struct sockaddr_in clientaddr; 64 | union { 65 | char raw[0xFFFF]; // UDP packet can't be larger then that 66 | dns_header h; 67 | } buf; 68 | ssize_t pktlen, outgoing; 69 | 70 | assert(fd == event_get_fd(&self->listener)); 71 | pktlen = red_recv_udp_pkt(fd, buf.raw, sizeof(buf), &clientaddr, NULL); 72 | if (pktlen == -1) 73 | return; 74 | 75 | if (pktlen <= sizeof(dns_header)) { 76 | dnstc_log_error(LOG_INFO, "incomplete DNS request"); 77 | return; 78 | } 79 | 80 | if (1 81 | && (buf.h.qr_opcode_aa_tc_rd & DNS_QR) == 0 /* query */ 82 | && (buf.h.ra_z_rcode & DNS_Z) == 0 /* Z is Zero */ 83 | && buf.h.qdcount /* some questions */ 84 | && !buf.h.ancount && !buf.h.nscount /* no answers */ 85 | ) { 86 | buf.h.qr_opcode_aa_tc_rd |= DNS_QR; 87 | buf.h.qr_opcode_aa_tc_rd |= DNS_TC; 88 | outgoing = sendto(fd, buf.raw, pktlen, 0, 89 | (struct sockaddr*)&clientaddr, sizeof(clientaddr)); 90 | if (outgoing != pktlen) 91 | dnstc_log_errno(LOG_WARNING, "sendto: I was sending %zd bytes, but only %zd were sent.", 92 | pktlen, outgoing); 93 | else 94 | dnstc_log_error(LOG_INFO, "sent truncated DNS reply"); 95 | } 96 | else { 97 | dnstc_log_error(LOG_INFO, "malformed DNS request"); 98 | } 99 | } 100 | 101 | /*********************************************************************** 102 | * Init / shutdown 103 | */ 104 | static parser_entry dnstc_entries[] = 105 | { 106 | { .key = "local_ip", .type = pt_in_addr }, 107 | { .key = "local_port", .type = pt_uint16 }, 108 | { } 109 | }; 110 | 111 | static list_head instances = LIST_HEAD_INIT(instances); 112 | 113 | static int dnstc_onenter(parser_section *section) 114 | { 115 | dnstc_instance *instance = calloc(1, sizeof(*instance)); 116 | if (!instance) { 117 | parser_error(section->context, "Not enough memory"); 118 | return -1; 119 | } 120 | 121 | INIT_LIST_HEAD(&instance->list); 122 | instance->config.bindaddr.sin_family = AF_INET; 123 | instance->config.bindaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); 124 | 125 | for (parser_entry *entry = §ion->entries[0]; entry->key; entry++) 126 | entry->addr = 127 | (strcmp(entry->key, "local_ip") == 0) ? (void*)&instance->config.bindaddr.sin_addr : 128 | (strcmp(entry->key, "local_port") == 0) ? (void*)&instance->config.bindaddr.sin_port : 129 | NULL; 130 | section->data = instance; 131 | return 0; 132 | } 133 | 134 | static int dnstc_onexit(parser_section *section) 135 | { 136 | dnstc_instance *instance = section->data; 137 | 138 | section->data = NULL; 139 | for (parser_entry *entry = §ion->entries[0]; entry->key; entry++) 140 | entry->addr = NULL; 141 | 142 | instance->config.bindaddr.sin_port = htons(instance->config.bindaddr.sin_port); 143 | 144 | list_add(&instance->list, &instances); 145 | 146 | return 0; 147 | } 148 | 149 | static int dnstc_init_instance(dnstc_instance *instance) 150 | { 151 | /* FIXME: dnstc_fini_instance is called in case of failure, this 152 | * function will remove instance from instances list - result 153 | * looks ugly. 154 | */ 155 | int error; 156 | int fd = -1; 157 | 158 | fd = socket(AF_INET, SOCK_DGRAM, 0); 159 | if (fd == -1) { 160 | log_errno(LOG_ERR, "socket"); 161 | goto fail; 162 | } 163 | 164 | error = bind(fd, (struct sockaddr*)&instance->config.bindaddr, sizeof(instance->config.bindaddr)); 165 | if (error) { 166 | log_errno(LOG_ERR, "bind"); 167 | goto fail; 168 | } 169 | 170 | error = fcntl_nonblock(fd); 171 | if (error) { 172 | log_errno(LOG_ERR, "fcntl"); 173 | goto fail; 174 | } 175 | 176 | event_set(&instance->listener, fd, EV_READ | EV_PERSIST, dnstc_pkt_from_client, instance); 177 | error = event_add(&instance->listener, NULL); 178 | if (error) { 179 | log_errno(LOG_ERR, "event_add"); 180 | goto fail; 181 | } 182 | 183 | return 0; 184 | 185 | fail: 186 | dnstc_fini_instance(instance); 187 | 188 | if (fd != -1) { 189 | if (close(fd) != 0) 190 | log_errno(LOG_WARNING, "close"); 191 | } 192 | 193 | return -1; 194 | } 195 | 196 | /* Drops instance completely, freeing its memory and removing from 197 | * instances list. 198 | */ 199 | static void dnstc_fini_instance(dnstc_instance *instance) 200 | { 201 | if (event_initialized(&instance->listener)) { 202 | if (event_del(&instance->listener) != 0) 203 | log_errno(LOG_WARNING, "event_del"); 204 | if (close(event_get_fd(&instance->listener)) != 0) 205 | log_errno(LOG_WARNING, "close"); 206 | memset(&instance->listener, 0, sizeof(instance->listener)); 207 | } 208 | 209 | list_del(&instance->list); 210 | 211 | memset(instance, 0, sizeof(*instance)); 212 | free(instance); 213 | } 214 | 215 | static int dnstc_init() 216 | { 217 | dnstc_instance *tmp, *instance = NULL; 218 | 219 | // TODO: init debug_dumper 220 | 221 | list_for_each_entry_safe(instance, tmp, &instances, list) { 222 | if (dnstc_init_instance(instance) != 0) 223 | goto fail; 224 | } 225 | 226 | return 0; 227 | 228 | fail: 229 | dnstc_fini(); 230 | return -1; 231 | } 232 | 233 | static int dnstc_fini() 234 | { 235 | dnstc_instance *tmp, *instance = NULL; 236 | 237 | list_for_each_entry_safe(instance, tmp, &instances, list) 238 | dnstc_fini_instance(instance); 239 | 240 | return 0; 241 | } 242 | 243 | static parser_section dnstc_conf_section = 244 | { 245 | .name = "dnstc", 246 | .entries = dnstc_entries, 247 | .onenter = dnstc_onenter, 248 | .onexit = dnstc_onexit 249 | }; 250 | 251 | app_subsys dnstc_subsys = 252 | { 253 | .init = dnstc_init, 254 | .fini = dnstc_fini, 255 | .conf_section = &dnstc_conf_section, 256 | }; 257 | 258 | /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ 259 | /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ 260 | -------------------------------------------------------------------------------- /dnstc.h: -------------------------------------------------------------------------------- 1 | #ifndef DNSTC_H 2 | #define DNSTC_H 3 | 4 | typedef struct dnstc_config_t { 5 | struct sockaddr_in bindaddr; 6 | } dnstc_config; 7 | 8 | typedef struct dnstc_instance_t { 9 | list_head list; 10 | dnstc_config config; 11 | struct event listener; 12 | } dnstc_instance; 13 | 14 | /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ 15 | /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ 16 | #endif /* REDUDP_H */ 17 | -------------------------------------------------------------------------------- /dnsu2t.h: -------------------------------------------------------------------------------- 1 | #ifndef DNSU2T_H 2 | #define DNSU2T_H 3 | 4 | #include "utils.h" 5 | 6 | typedef struct dnsu2t_config_t { 7 | struct sockaddr_in bindaddr; 8 | struct sockaddr_in relayaddr; 9 | uint16_t relay_timeout; 10 | uint16_t inflight_max; 11 | } dnsu2t_config; 12 | 13 | typedef struct dns_header_t { 14 | uint16_t id; 15 | uint8_t qr_opcode_aa_tc_rd; 16 | uint8_t ra_z_rcode; 17 | uint16_t qdcount; 18 | uint16_t ancount; 19 | uint16_t nscount; 20 | uint16_t arcount; // may be >0 for EDNS queries 21 | } PACKED dns_header; 22 | 23 | #define DNS_QR 0x80 24 | #define DNS_TC 0x02 25 | #define DNS_Z 0x70 26 | 27 | typedef struct dns_tcp_pkt_t { 28 | uint16_t sz; 29 | union { 30 | dns_header hdr; 31 | char raw[0xffff]; 32 | } dns; 33 | } PACKED dns_tcp_pkt; 34 | 35 | typedef struct dnsu2t_instance_t { 36 | list_head list; 37 | dnsu2t_config config; 38 | struct event listener; 39 | 40 | struct event relay_rd; 41 | struct event relay_wr; 42 | 43 | bool reqstream_broken; 44 | int request_count; 45 | int inflight_count; 46 | void* inflight_root; 47 | 48 | ssize_t pkt_size; 49 | dns_tcp_pkt pkt; 50 | } dnsu2t_instance; 51 | 52 | /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ 53 | /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ 54 | #endif /* DNSU2T_H */ 55 | -------------------------------------------------------------------------------- /doc/balabit-TPROXY-README.txt: -------------------------------------------------------------------------------- 1 | 2 | These are the Transparent Proxying patches for Linux kernel 2.6. 3 | 4 | The latest version can always be found at 5 | 6 | http://www.balabit.com/download/files/tproxy/ 7 | 8 | 9 | What does the term 'proxy' mean? 10 | -------------------------------- 11 | 12 | A proxy is a server-like program, receiving requests from clients, 13 | forwarding those requests to the real server on behalf of users, 14 | and returning the response as it arrives. 15 | 16 | Proxies read and parse the application protocol, and reject invalid 17 | traffic. As most attacks violate the application protocol, disallowing 18 | protocol violations usually protects against attacks. 19 | 20 | What is transparent proxying? 21 | ----------------------------- 22 | 23 | To simplify management tasks of clients sitting behind proxy 24 | firewalls, the technique 'transparent proxying' was invented. 25 | Transparent proxying means that the presence of the proxy is invisible 26 | to the user. Transparent proxying however requires kernel support. 27 | 28 | We have a 'REDIRECT' target, isn't that enough? 29 | ---------------------------------------------- 30 | 31 | Real transparent proxying requires the following three features from 32 | the IP stack of the computer it is running on: 33 | 1. Redirect sessions destined to the outer network to a local process 34 | using a packet filter rule. 35 | 2. Make it possible for a process to listen to connections on a 36 | foreign address. 37 | 3. Make it possible for a process to initiate a connection with a 38 | foreign address as a source. 39 | 40 | Item #1 is usually provided by packet filtering packages like 41 | Netfilter/IPTables, IPFilter. (yes, this is the REDIRECT target) 42 | 43 | All three were provided in Linux kernels 2.2.x, but support for this 44 | was removed. 45 | 46 | How to install it? 47 | ------------------ 48 | 49 | Download the latest tproxy-kernel-*.tar.bz2 tarball 50 | for your kernel (from v2.6.24), with the tproxy-iptables-*.patch file. 51 | 52 | Patch your kernel using: 53 | 54 | cd /usr/src/linux 55 | cat /00*.patch | patch -p1 56 | 57 | then enable tproxy support, `socket' and `TPROXY' modules 58 | (with optional conntrack support if you need SNAT), compile your kernel 59 | and modules. 60 | 61 | The required modules are automatically loaded if the iptables commands 62 | are used. 63 | 64 | The IPtables patches: 65 | 66 | cd /usr/src/iptables-1.4.X 67 | cat /tproxy-iptables*.patch | patch -p1 68 | 69 | then compile it on the usual way: 70 | 71 | ./autogen.sh 72 | ./configure && make && make install 73 | 74 | Squid-3 has official support of TProxy v4.1: 75 | 76 | checkout the source code of squid-3 as in 77 | 78 | http://wiki.squid-cache.org/Squid3VCS 79 | 80 | 81 | then compile it: 82 | 83 | cd ~/source/squid 84 | ./bootstrap.sh 85 | ./configure --enable-linux-netfilter && make && make install 86 | 87 | Of course you might need to change the path in the examples above. 88 | 89 | How to start using it? 90 | ---------------------- 91 | 92 | This implementation of transparent proxying works by marking packets and 93 | changing the route based on packet mark. The foreign address bind and tproxy 94 | redirection is enabled via a new socket option, IP_TRANSPARENT, without it 95 | neither the bind nor the tproxy target works. 96 | 97 | Now let's see what happens when a proxy tries to use the required tproxy 98 | features I outlined earlier. 99 | 100 | 1. Redirection 101 | 102 | This is easy, as this was already supported by iptables. Redirection is 103 | equivalent with the following nat rule: 104 | 105 | iptables -t nat -A PREROUTING -j DNAT --to-dest --to-port 106 | 107 | is one the IP address of the interface where the packet 108 | entered the IP stack 109 | is the port where the proxy was bound to 110 | 111 | To indicate that this is not simple NAT rule, a separate target, 'TPROXY' 112 | was created: 113 | 114 | iptables -t mangle -A PREROUTING -p tcp --dport 80 -j TPROXY --on-port \ 115 | --tproxy-mark 0x1/0x1 116 | 117 | The local IP address is determined automatically, but can be overridden 118 | by the --on-ip parameter. 119 | 120 | The marked sockets has to be routed locally: 121 | 122 | ip rule add fwmark 1 lookup 100 123 | ip route add local 0.0.0.0/0 dev lo table 100 124 | 125 | 126 | 2. Listening for connections on a foreign address 127 | 128 | There are protocols which use more than a single TCP channel for 129 | communication. The best example is FTP which uses a command channel for 130 | sending commands, and a data channel to transfer the body of files. The 131 | secondary channel can be established in both active and passive mode, 132 | active meaning the server connects back to the client, passive meaning 133 | the client connects to the server on another port. 134 | 135 | Let's see the passive case, when the client establishes a connection to 136 | the address returned in the response of the PASV FTP command. 137 | 138 | As the presence of the proxy is transparent to the client, the target 139 | IP address of the secondary channel (e.g. the address in the PASV 140 | response) is the server (and not the firewall) and this connection must 141 | also be handled by the proxy. 142 | 143 | The first solution that comes to mind is to add a a TPROXY rule 144 | automatically (e.g. to redirect a connection destined to a given server 145 | on a given port to a local process), however it is not feasible, adding 146 | rules on the fly should not be required as it would mess the 147 | administrator's own rules, the NAT translation should be done 148 | implicitly without touching the user rulebase. 149 | 150 | To do this on a Linux 2.2 kernel it was enough to call bind() on a 151 | socket with a foreign IP address, and if a new connection to the given 152 | foreign IP was routed through the firewall the connection was 153 | intercepted. This solution however distracted the core network kernel 154 | hackers and removed this feature. This implementation is similar to 155 | the old behaviour although it works a bit differently: 156 | 157 | * the proxy sets the IP_TRANSPARENT socket option on the listening 158 | socket 159 | * the proxy then binds to the foreign address 160 | * the proxy accepts incoming connections 161 | 162 | It requires additional iptables rules with the socket module of the 163 | tproxy patches: 164 | 165 | iptables -t mangle -N DIVERT 166 | iptables -t mangle -A PREROUTING -p tcpo -m socket -j DIVERT 167 | iptables -t mangle -A DIVERT -j MARK --set-xmark 0x1/0xffffffff 168 | iptables -t mangle -A DIVERT -j ACCEPT 169 | 170 | the best if the second rule is before using the TPROXY target. 171 | 172 | 3. Initiating connections with a foreign address as a source 173 | 174 | Similarly to the case outlined above, it is sometimes necessary to be 175 | able to initiate a connection with a foreign IP address as a source. 176 | Imagine the active FTP case when the FTP client listens for connections 177 | with source address equal to the server. Another example: a webserver 178 | in your DMZ which does access control based on client IP address. If 179 | the proxy could not initiate connections with foreign IP address, the 180 | webserver would see the inner IP address of the firewall itself. 181 | 182 | In Linux 2.2 this was accomplished by bind()-ing to a foreign address 183 | prior calling connect(), and it worked. In this tproxy patch it is done 184 | somewhat similar to the case 2 outlined above. 185 | 186 | * the proxy calls setsockopt with IP_TRANSPARENT 187 | 188 | * the proxy bind to a foreign address 189 | 190 | * the tproxy calls connect() 191 | 192 | The iptables rules with the socket match are also required here. 193 | 194 | How to use it? 195 | -------------- 196 | 197 | The following use-case assumes a transparent proxy listening on port 198 | 50080 and any ip address (0.0.0.0). 199 | 200 | First, set up the routing rules with iproute2: 201 | 202 | ip rule add fwmark 1 lookup 100 203 | ip route add local 0.0.0.0/0 dev lo table 100 204 | 205 | Or, if you want to use packet marking for anything else, the least 206 | significant bit is enough for transparent proxying. 207 | 208 | ip rule add fwmark 0x1/0x1 lookup 100 209 | ip route add local 0.0.0.0/0 dev lo table 100 210 | 211 | Note that this latter example is only working with newer versions of 212 | iproute2. 213 | 214 | For supporting foreign address bind, the socket match is required with 215 | packet marking: 216 | 217 | iptables -t mangle -N DIVERT 218 | iptables -t mangle -A PREROUTING -p tcp -m socket -j DIVERT 219 | 220 | # DIVERT chain: mark packets and accept 221 | iptables -t mangle -A DIVERT -j MARK --set-mark 1 222 | iptables -t mangle -A DIVERT -j ACCEPT 223 | 224 | The last rule is for diverting traffic to the proxy: 225 | 226 | iptables -t mangle -A PREROUTING -p tcp --dport 80 -j TPROXY \ 227 | --tproxy-mark 0x1/0x1 --on-port 50080 228 | 229 | If it is a Squid-3 proxy, in /etc/squid/squid.conf the following 230 | rule is necessary for transparent proxying: 231 | 232 | http_port 50080 tproxy transparent 233 | 234 | Then set up the ACL rules according to your local policy. 235 | 236 | -------------------------------------------------------------------------------- /doc/iptables-packet-flow-ng.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samyk/redsocks/1edc820d51a9b9e6829bad8455f39c1244cf706f/doc/iptables-packet-flow-ng.png -------------------------------------------------------------------------------- /doc/iptables-packet-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samyk/redsocks/1edc820d51a9b9e6829bad8455f39c1244cf706f/doc/iptables-packet-flow.png -------------------------------------------------------------------------------- /doc/rfc1929-socks5-auth.txt: -------------------------------------------------------------------------------- 1 | 2 | Network Working Group M. Leech 3 | Request for Comments: 1929 Bell-Northern Research Ltd 4 | Category: Standards Track March 1996 5 | 6 | Username/Password Authentication for SOCKS V5 7 | 8 | Status of this Memo 9 | 10 | This document specifies an Internet standards track protocol for the 11 | Internet community, and requests discussion and suggestions for 12 | improvements. Please refer to the current edition of the "Internet 13 | Official Protocol Standards" (STD 1) for the standardization state 14 | and status of this protocol. Distribution of this memo is unlimited. 15 | 16 | 1. Introduction 17 | 18 | The protocol specification for SOCKS Version 5 specifies a 19 | generalized framework for the use of arbitrary authentication 20 | protocols in the initial socks connection setup. This document 21 | describes one of those protocols, as it fits into the SOCKS Version 5 22 | authentication "subnegotiation". 23 | 24 | Note: 25 | 26 | Unless otherwise noted, the decimal numbers appearing in packet- 27 | format diagrams represent the length of the corresponding field, in 28 | octets. Where a given octet must take on a specific value, the 29 | syntax X'hh' is used to denote the value of the single octet in that 30 | field. When the word 'Variable' is used, it indicates that the 31 | corresponding field has a variable length defined either by an 32 | associated (one or two octet) length field, or by a data type field. 33 | 34 | 2. Initial negotiation 35 | 36 | Once the SOCKS V5 server has started, and the client has selected the 37 | Username/Password Authentication protocol, the Username/Password 38 | subnegotiation begins. This begins with the client producing a 39 | Username/Password request: 40 | 41 | +----+------+----------+------+----------+ 42 | |VER | ULEN | UNAME | PLEN | PASSWD | 43 | +----+------+----------+------+----------+ 44 | | 1 | 1 | 1 to 255 | 1 | 1 to 255 | 45 | +----+------+----------+------+----------+ 46 | 47 | Leech Standards Track [Page 1] 48 | 49 | RFC 1929 Username Authentication for SOCKS V5 March 1996 50 | 51 | The VER field contains the current version of the subnegotiation, 52 | which is X'01'. The ULEN field contains the length of the UNAME field 53 | that follows. The UNAME field contains the username as known to the 54 | source operating system. The PLEN field contains the length of the 55 | PASSWD field that follows. The PASSWD field contains the password 56 | association with the given UNAME. 57 | 58 | The server verifies the supplied UNAME and PASSWD, and sends the 59 | following response: 60 | 61 | +----+--------+ 62 | |VER | STATUS | 63 | +----+--------+ 64 | | 1 | 1 | 65 | +----+--------+ 66 | 67 | A STATUS field of X'00' indicates success. If the server returns a 68 | `failure' (STATUS value other than X'00') status, it MUST close the 69 | connection. 70 | 71 | 3. Security Considerations 72 | 73 | This document describes a subnegotiation that provides authentication 74 | services to the SOCKS protocol. Since the request carries the 75 | password in cleartext, this subnegotiation is not recommended for 76 | environments where "sniffing" is possible and practical. 77 | 78 | 4. Author's Address 79 | 80 | Marcus Leech 81 | Bell-Northern Research Ltd 82 | P.O. Box 3511, Station C 83 | Ottawa, ON 84 | CANADA K1Y 4H7 85 | 86 | Phone: +1 613 763 9145 87 | EMail: mleech@bnr.ca 88 | 89 | Leech Standards Track [Page 2] 90 | -------------------------------------------------------------------------------- /doc/socks4.protocol.txt: -------------------------------------------------------------------------------- 1 | SOCKS: A protocol for TCP proxy across firewalls 2 | 3 | Ying-Da Lee 4 | Principal Member Technical Staff 5 | NEC Systems Laboratory, CSTC 6 | ylee@syl.dl.nec.com 7 | 8 | SOCKS was originally developed by David Koblas and subsequently modified 9 | and extended by me to its current running version -- version 4. It is a 10 | protocol that relays TCP sessions at a firewall host to allow application 11 | users transparent access across the firewall. Because the protocol is 12 | independent of application protocols, it can be (and has been) used for 13 | many different services, such as telnet, ftp, finger, whois, gopher, WWW, 14 | etc. Access control can be applied at the beginning of each TCP session; 15 | thereafter the server simply relays the data between the client and the 16 | application server, incurring minimum processing overhead. Since SOCKS 17 | never has to know anything about the application protocol, it should also 18 | be easy for it to accommodate applications which use encryption to protect 19 | their traffic from nosey snoopers. 20 | 21 | Two operations are defined: CONNECT and BIND. 22 | 23 | 1) CONNECT 24 | 25 | The client connects to the SOCKS server and sends a CONNECT request when 26 | it wants to establish a connection to an application server. The client 27 | includes in the request packet the IP address and the port number of the 28 | destination host, and userid, in the following format. 29 | 30 | +----+----+----+----+----+----+----+----+----+----+....+----+ 31 | | VN | CD | DSTPORT | DSTIP | USERID |NULL| 32 | +----+----+----+----+----+----+----+----+----+----+....+----+ 33 | # of bytes: 1 1 2 4 variable 1 34 | 35 | VN is the SOCKS protocol version number and should be 4. CD is the 36 | SOCKS command code and should be 1 for CONNECT request. NULL is a byte 37 | of all zero bits. 38 | 39 | The SOCKS server checks to see whether such a request should be granted 40 | based on any combination of source IP address, destination IP address, 41 | destination port number, the userid, and information it may obtain by 42 | consulting IDENT, cf. RFC 1413. If the request is granted, the SOCKS 43 | server makes a connection to the specified port of the destination host. 44 | A reply packet is sent to the client when this connection is established, 45 | or when the request is rejected or the operation fails. 46 | 47 | +----+----+----+----+----+----+----+----+ 48 | | VN | CD | DSTPORT | DSTIP | 49 | +----+----+----+----+----+----+----+----+ 50 | # of bytes: 1 1 2 4 51 | 52 | VN is the version of the reply code and should be 0. CD is the result 53 | code with one of the following values: 54 | 55 | 90: request granted 56 | 91: request rejected or failed 57 | 92: request rejected becasue SOCKS server cannot connect to 58 | identd on the client 59 | 93: request rejected because the client program and identd 60 | report different user-ids 61 | 62 | The remaining fields are ignored. 63 | 64 | The SOCKS server closes its connection immediately after notifying 65 | the client of a failed or rejected request. For a successful request, 66 | the SOCKS server gets ready to relay traffic on both directions. This 67 | enables the client to do I/O on its connection as if it were directly 68 | connected to the application server. 69 | 70 | 71 | 2) BIND 72 | 73 | The client connects to the SOCKS server and sends a BIND request when 74 | it wants to prepare for an inbound connection from an application server. 75 | This should only happen after a primary connection to the application 76 | server has been established with a CONNECT. Typically, this is part of 77 | the sequence of actions: 78 | 79 | -bind(): obtain a socket 80 | -getsockname(): get the IP address and port number of the socket 81 | -listen(): ready to accept call from the application server 82 | -use the primary connection to inform the application server of 83 | the IP address and the port number that it should connect to. 84 | -accept(): accept a connection from the application server 85 | 86 | The purpose of SOCKS BIND operation is to support such a sequence 87 | but using a socket on the SOCKS server rather than on the client. 88 | 89 | The client includes in the request packet the IP address of the 90 | application server, the destination port used in the primary connection, 91 | and the userid. 92 | 93 | +----+----+----+----+----+----+----+----+----+----+....+----+ 94 | | VN | CD | DSTPORT | DSTIP | USERID |NULL| 95 | +----+----+----+----+----+----+----+----+----+----+....+----+ 96 | # of bytes: 1 1 2 4 variable 1 97 | 98 | VN is again 4 for the SOCKS protocol version number. CD must be 2 to 99 | indicate BIND request. 100 | 101 | The SOCKS server uses the client information to decide whether the 102 | request is to be granted. The reply it sends back to the client has 103 | the same format as the reply for CONNECT request, i.e., 104 | 105 | +----+----+----+----+----+----+----+----+ 106 | | VN | CD | DSTPORT | DSTIP | 107 | +----+----+----+----+----+----+----+----+ 108 | # of bytes: 1 1 2 4 109 | 110 | VN is the version of the reply code and should be 0. CD is the result 111 | code with one of the following values: 112 | 113 | 90: request granted 114 | 91: request rejected or failed 115 | 92: request rejected becasue SOCKS server cannot connect to 116 | identd on the client 117 | 93: request rejected because the client program and identd 118 | report different user-ids. 119 | 120 | However, for a granted request (CD is 90), the DSTPORT and DSTIP fields 121 | are meaningful. In that case, the SOCKS server obtains a socket to wait 122 | for an incoming connection and sends the port number and the IP address 123 | of that socket to the client in DSTPORT and DSTIP, respectively. If the 124 | DSTIP in the reply is 0 (the value of constant INADDR_ANY), then the 125 | client should replace it by the IP address of the SOCKS server to which 126 | the cleint is connected. (This happens if the SOCKS server is not a 127 | multi-homed host.) In the typical scenario, these two numbers are 128 | made available to the application client prgram via the result of the 129 | subsequent getsockname() call. The application protocol must provide a 130 | way for these two pieces of information to be sent from the client to 131 | the application server so that it can initiate the connection, which 132 | connects it to the SOCKS server rather than directly to the application 133 | client as it normally would. 134 | 135 | The SOCKS server sends a second reply packet to the client when the 136 | anticipated connection from the application server is established. 137 | The SOCKS server checks the IP address of the originating host against 138 | the value of DSTIP specified in the client's BIND request. If a mismatch 139 | is found, the CD field in the second reply is set to 91 and the SOCKS 140 | server closes both connections. If the two match, CD in the second 141 | reply is set to 90 and the SOCKS server gets ready to relay the traffic 142 | on its two connections. From then on the client does I/O on its connection 143 | to the SOCKS server as if it were directly connected to the application 144 | server. 145 | 146 | 147 | 148 | For both CONNECT and BIND operations, the server sets a time limit 149 | (2 minutes in current CSTC implementation) for the establishment of its 150 | connection with the application server. If the connection is still not 151 | establiched when the time limit expires, the server closes its connection 152 | to the client and gives up. 153 | -------------------------------------------------------------------------------- /doc/socks4a.protocol.txt: -------------------------------------------------------------------------------- 1 | SOCKS 4A: A Simple Extension to SOCKS 4 Protocol 2 | 3 | Ying-Da Lee 4 | yingda@best.com or yingda@esd.sgi.com 5 | 6 | Please read SOCKS4.protocol first for an description of the version 4 7 | protocol. This extension is intended to allow the use of SOCKS on hosts 8 | which are not capable of resolving all domain names. 9 | 10 | In version 4, the client sends the following packet to the SOCKS server 11 | to request a CONNECT or a BIND operation: 12 | 13 | +----+----+----+----+----+----+----+----+----+----+....+----+ 14 | | VN | CD | DSTPORT | DSTIP | USERID |NULL| 15 | +----+----+----+----+----+----+----+----+----+----+....+----+ 16 | # of bytes: 1 1 2 4 variable 1 17 | 18 | VN is the SOCKS protocol version number and should be 4. CD is the 19 | SOCKS command code and should be 1 for CONNECT or 2 for BIND. NULL 20 | is a byte of all zero bits. 21 | 22 | For version 4A, if the client cannot resolve the destination host's 23 | domain name to find its IP address, it should set the first three bytes 24 | of DSTIP to NULL and the last byte to a non-zero value. (This corresponds 25 | to IP address 0.0.0.x, with x nonzero. As decreed by IANA -- The 26 | Internet Assigned Numbers Authority -- such an address is inadmissible 27 | as a destination IP address and thus should never occur if the client 28 | can resolve the domain name.) Following the NULL byte terminating 29 | USERID, the client must sends the destination domain name and termiantes 30 | it with another NULL byte. This is used for both CONNECT and BIND requests. 31 | 32 | A server using protocol 4A must check the DSTIP in the request packet. 33 | If it represent address 0.0.0.x with nonzero x, the server must read 34 | in the domain name that the client sends in the packet. The server 35 | should resolve the domain name and make connection to the destination 36 | host if it can. 37 | 38 | SOCKSified sockd may pass domain names that it cannot resolve to 39 | the next-hop SOCKS server. 40 | -------------------------------------------------------------------------------- /http-auth.c: -------------------------------------------------------------------------------- 1 | /* redsocks - transparent TCP-to-proxy redirector 2 | * Copyright (C) 2007-2018 Leonid Evdokimov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | * 16 | * 17 | * http-auth library, provide basic and digest scheme 18 | * see RFC 2617 for details 19 | */ 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include "md5.h" 27 | #include "base64.h" 28 | #include "log.h" 29 | #include "http-auth.h" 30 | 31 | char* basic_authentication_encode(const char *user, const char *passwd) 32 | { 33 | /* prepare the user:pass key pair */ 34 | int pair_len = strlen(user) + 1 + strlen(passwd); 35 | char pair[pair_len + 1]; 36 | 37 | sprintf(pair, "%s:%s", user, passwd); 38 | 39 | /* calculate the final string length */ 40 | int basic_len = BASE64_SIZE(pair_len); 41 | char *basic_ptr = calloc(basic_len + 1, 1); 42 | 43 | if (!base64_encode(basic_ptr, basic_len, (const uint8_t*)pair, pair_len)) 44 | return NULL; 45 | 46 | return basic_ptr; 47 | } 48 | 49 | #define MD5_HASHLEN (16) 50 | 51 | static void dump_hash(char *buf, const unsigned char *hash) 52 | { 53 | int i; 54 | 55 | for (i = 0; i < MD5_HASHLEN; i++) { 56 | buf += sprintf(buf, "%02x", hash[i]); 57 | } 58 | 59 | *buf = 0; 60 | } 61 | 62 | typedef struct { 63 | const char *b, *e; 64 | } param_token; 65 | 66 | /* Extract a parameter from the string (typically an HTTP header) at 67 | **SOURCE and advance SOURCE to the next parameter. Return false 68 | when there are no more parameters to extract. The name of the 69 | parameter is returned in NAME, and the value in VALUE. If the 70 | parameter has no value, the token's b==e.*/ 71 | 72 | static int extract_param(const char **source, param_token *name, param_token *value, char separator) 73 | { 74 | const char *p = *source; 75 | 76 | while (isspace (*p)) 77 | ++p; 78 | 79 | if (!*p) 80 | { 81 | *source = p; 82 | return 0; /* no error; nothing more to extract */ 83 | } 84 | 85 | /* Extract name. */ 86 | name->b = p; 87 | while (*p && !isspace (*p) && *p != '=' && *p != separator) 88 | ++p; 89 | name->e = p; 90 | if (name->b == name->e) 91 | return 0; /* empty name: error */ 92 | 93 | while (isspace (*p)) 94 | ++p; 95 | if (*p == separator || !*p) /* no value */ 96 | { 97 | value->b = value->e = ""; 98 | if (*p == separator) ++p; 99 | *source = p; 100 | return 1; 101 | } 102 | if (*p != '=') 103 | return 0; /* error */ 104 | 105 | /* *p is '=', extract value */ 106 | ++p; 107 | while (isspace (*p)) ++p; 108 | if (*p == '"') /* quoted */ 109 | { 110 | value->b = ++p; 111 | while (*p && *p != '"') ++p; 112 | if (!*p) 113 | return 0; 114 | value->e = p++; 115 | /* Currently at closing quote; find the end of param. */ 116 | while (isspace (*p)) ++p; 117 | while (*p && *p != separator) ++p; 118 | if (*p == separator) 119 | ++p; 120 | else if (*p) 121 | /* garbage after closed quote, e.g. foo="bar"baz */ 122 | return 0; 123 | } 124 | else /* unquoted */ 125 | { 126 | value->b = p; 127 | while (*p && *p != separator) ++p; 128 | value->e = p; 129 | while (value->e != value->b && isspace (value->e[-1])) 130 | --value->e; 131 | if (*p == separator) ++p; 132 | } 133 | *source = p; 134 | return 1; 135 | 136 | } 137 | 138 | char* digest_authentication_encode(const char *line, const char *user, const char *passwd, 139 | const char *method, const char *path, int count, const char *cnonce) 140 | { 141 | char *realm = NULL, *opaque = NULL, *nonce = NULL, *qop = NULL; 142 | char nc[9]; 143 | sprintf(nc, "%08x", count); 144 | 145 | const char *ptr = line; 146 | param_token name, value; 147 | 148 | while (extract_param(&ptr, &name, &value, ',')) { 149 | int namelen = name.e - name.b; 150 | int valuelen = value.e - value.b; 151 | 152 | if (strncasecmp(name.b, "realm" , namelen) == 0) { 153 | strncpy(realm = calloc(valuelen + 1, 1), value.b, valuelen); 154 | realm[valuelen] = '\0'; 155 | } 156 | else if (strncasecmp(name.b, "opaque", namelen) == 0) { 157 | strncpy(opaque = calloc(valuelen + 1, 1), value.b, valuelen); 158 | opaque[valuelen] = '\0'; 159 | } 160 | else if (strncasecmp(name.b, "nonce" , namelen) == 0) { 161 | strncpy(nonce = calloc(valuelen + 1, 1), value.b, valuelen); 162 | nonce[valuelen] = '\0'; 163 | } 164 | else if (strncasecmp(name.b, "qop" , namelen) == 0) { 165 | strncpy(qop = calloc(valuelen + 1, 1), value.b, valuelen); 166 | qop[valuelen] = '\0'; 167 | } 168 | } 169 | 170 | if (!realm || !nonce || !user || !passwd || !path || !method) { 171 | free(realm); 172 | free(opaque); 173 | free(nonce); 174 | free(qop); 175 | return NULL; 176 | } 177 | 178 | if (qop && strncasecmp(qop, "auth", 5) != 0) { 179 | /* FIXME: currently don't support auth-int, only "auth" is supported */ 180 | free(realm); 181 | free(opaque); 182 | free(nonce); 183 | free(qop); 184 | return NULL; 185 | } 186 | 187 | /* calculate the digest value */ 188 | md5_state_t ctx; 189 | md5_byte_t hash[MD5_HASHLEN]; 190 | char a1buf[MD5_HASHLEN * 2 + 1], a2buf[MD5_HASHLEN * 2 + 1]; 191 | char response[MD5_HASHLEN * 2 + 1]; 192 | 193 | /* A1 = username-value ":" realm-value ":" passwd */ 194 | md5_init(&ctx); 195 | md5_append(&ctx, (md5_byte_t*)user, strlen(user)); 196 | md5_append(&ctx, (md5_byte_t*)":", 1); 197 | md5_append(&ctx, (md5_byte_t*)realm, strlen(realm)); 198 | md5_append(&ctx, (md5_byte_t*)":", 1); 199 | md5_append(&ctx, (md5_byte_t*)passwd, strlen(passwd)); 200 | md5_finish(&ctx, hash); 201 | dump_hash(a1buf, hash); 202 | 203 | /* A2 = Method ":" digest-uri-value */ 204 | md5_init(&ctx); 205 | md5_append(&ctx, (md5_byte_t*)method, strlen(method)); 206 | md5_append(&ctx, (md5_byte_t*)":", 1); 207 | md5_append(&ctx, (md5_byte_t*)path, strlen(path)); 208 | md5_finish(&ctx, hash); 209 | dump_hash(a2buf, hash); 210 | 211 | /* qop set: request-digest = H(A1) ":" nonce-value ":" nc-value ":" cnonce-value ":" qop-value ":" H(A2) */ 212 | /* not set: request-digest = H(A1) ":" nonce-value ":" H(A2) */ 213 | md5_init(&ctx); 214 | md5_append(&ctx, (md5_byte_t*)a1buf, strlen(a1buf)); 215 | md5_append(&ctx, (md5_byte_t*)":", 1); 216 | md5_append(&ctx, (md5_byte_t*)nonce, strlen(nonce)); 217 | md5_append(&ctx, (md5_byte_t*)":", 1); 218 | if (qop) { 219 | md5_append(&ctx, (md5_byte_t*)nc, strlen(nc)); 220 | md5_append(&ctx, (md5_byte_t*)":", 1); 221 | md5_append(&ctx, (md5_byte_t*)cnonce, strlen(cnonce)); 222 | md5_append(&ctx, (md5_byte_t*)":", 1); 223 | md5_append(&ctx, (md5_byte_t*)qop, strlen(qop)); 224 | md5_append(&ctx, (md5_byte_t*)":", 1); 225 | } 226 | md5_append(&ctx, (md5_byte_t*)a2buf, strlen(a2buf)); 227 | md5_finish(&ctx, hash); 228 | dump_hash(response, hash); 229 | 230 | /* prepare the final string */ 231 | int len = 256; 232 | len += strlen(user); 233 | len += strlen(realm); 234 | len += strlen(nonce); 235 | len += strlen(path); 236 | len += strlen(response); 237 | 238 | if (qop) { 239 | len += strlen(qop); 240 | len += strlen(nc); 241 | len += strlen(cnonce); 242 | } 243 | 244 | if (opaque) { 245 | len += strlen(opaque); 246 | } 247 | 248 | char *res = (char*)malloc(len); 249 | if (!qop) { 250 | sprintf(res, "username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", response=\"%s\"", 251 | user, realm, nonce, path, response); 252 | } else { 253 | sprintf(res, "username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", response=\"%s\", qop=%s, nc=%s, cnonce=\"%s\"", 254 | user, realm, nonce, path, response, qop, nc, cnonce); 255 | } 256 | 257 | if (opaque) { 258 | char *p = res + strlen(res); 259 | strcat (p, ", opaque=\""); 260 | strcat (p, opaque); 261 | strcat (p, "\""); 262 | } 263 | 264 | free(realm); 265 | free(opaque); 266 | free(nonce); 267 | free(qop); 268 | return res; 269 | } 270 | 271 | const char *auth_request_header = "Proxy-Authenticate:"; 272 | const char *auth_response_header = "Proxy-Authorization:"; 273 | 274 | char *http_auth_request_header(struct evbuffer *src, struct evbuffer *tee) 275 | { 276 | char *line; 277 | for (;;) { 278 | line = redsocks_evbuffer_readline(src); 279 | if (tee && line) { 280 | if (evbuffer_add(tee, line, strlen(line)) != 0 || 281 | evbuffer_add(tee, "\r\n", 2) != 0) 282 | { 283 | log_error(LOG_NOTICE, "evbuffer_add"); 284 | free(line); 285 | return NULL; // I'm going up straight to the 403... 286 | } 287 | } 288 | // FIXME: multi-line headers are not supported 289 | if (line == NULL || *line == '\0' || strchr(line, ':') == NULL) { 290 | free(line); 291 | return NULL; 292 | } 293 | if (strncasecmp(line, auth_request_header, strlen(auth_request_header)) == 0) 294 | return line; 295 | free(line); 296 | } 297 | } 298 | -------------------------------------------------------------------------------- /http-auth.h: -------------------------------------------------------------------------------- 1 | #ifndef HTTP_AUTH_H 2 | #define HTTP_AUTH_H 3 | 4 | #include "redsocks.h" 5 | 6 | typedef struct http_auth_t { 7 | char *last_auth_query; 8 | int last_auth_count; 9 | } http_auth; 10 | 11 | static inline http_auth* red_http_auth(redsocks_instance *i) 12 | { 13 | return (http_auth*)(i + 1); 14 | } 15 | 16 | /* 17 | * Create the authentication header contents for the `Basic' scheme. 18 | * This is done by encoding the string "USER:PASS" to base64 and 19 | * prepending the string "Basic " in front of it. 20 | * 21 | */ 22 | 23 | char* basic_authentication_encode(const char *user, const char *passwd); 24 | 25 | /* 26 | * Create the authentication header contents for the 'Digest' scheme. 27 | * only md5 method is available, see RFC 2617 for detail. 28 | * 29 | */ 30 | char* digest_authentication_encode(const char *line, const char *user, const char *passwd, 31 | const char *method, const char *path, int count, const char *cnonce); 32 | 33 | char *http_auth_request_header(struct evbuffer *src, struct evbuffer *tee); 34 | 35 | #endif /* HTTP_AUTH_H */ 36 | -------------------------------------------------------------------------------- /http-connect.c: -------------------------------------------------------------------------------- 1 | /* redsocks - transparent TCP-to-proxy redirector 2 | * Copyright (C) 2007-2018 Leonid Evdokimov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | * 16 | * 17 | * http-connect upstream module for redsocks 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include "log.h" 31 | #include "redsocks.h" 32 | #include "http-auth.h" 33 | 34 | typedef enum httpc_state_t { 35 | httpc_new, 36 | httpc_request_sent, 37 | httpc_reply_came, // 200 OK came, skipping headers... 38 | httpc_headers_skipped, // starting pump! 39 | httpc_no_way, // proxy can't handle the request 40 | httpc_MAX, 41 | } httpc_state; 42 | 43 | 44 | #define HTTP_HEAD_WM_HIGH 4096 // that should be enough for one HTTP line. 45 | 46 | 47 | static void httpc_client_init(redsocks_client *client) 48 | { 49 | client->state = httpc_new; 50 | } 51 | 52 | static void httpc_instance_fini(redsocks_instance *instance) 53 | { 54 | http_auth *auth = red_http_auth(instance); 55 | free(auth->last_auth_query); 56 | auth->last_auth_query = NULL; 57 | } 58 | 59 | static struct evbuffer *httpc_mkconnect(redsocks_client *client); 60 | 61 | extern const char *auth_request_header; 62 | extern const char *auth_response_header; 63 | 64 | static void httpc_read_cb(struct bufferevent *buffev, void *_arg) 65 | { 66 | redsocks_client *client = _arg; 67 | 68 | assert(client->relay == buffev); 69 | assert(client->state == httpc_request_sent || client->state == httpc_reply_came); 70 | 71 | redsocks_touch_client(client); 72 | 73 | // evbuffer_add() triggers callbacks, so we can't write to client->client 74 | // till we know that we're going to ONFAIL_FORWARD_HTTP_ERR. 75 | // And the decision is made when all the headers are processed. 76 | struct evbuffer* tee = NULL; 77 | const bool do_errtee = client->instance->config.on_proxy_fail == ONFAIL_FORWARD_HTTP_ERR; 78 | 79 | if (client->state == httpc_request_sent) { 80 | size_t len = evbuffer_get_length(buffev->input); 81 | char *line = redsocks_evbuffer_readline(buffev->input); 82 | if (line) { 83 | unsigned int code; 84 | if (sscanf(line, "HTTP/%*u.%*u %u", &code) == 1) { // 1 == one _assigned_ match 85 | if (code == 407) { // auth failed 86 | http_auth *auth = red_http_auth(client->instance); 87 | 88 | if (auth->last_auth_query != NULL && auth->last_auth_count == 1) { 89 | redsocks_log_error(client, LOG_NOTICE, "HTTP Proxy auth failed: %s", line); 90 | client->state = httpc_no_way; 91 | } else if (client->instance->config.login == NULL || client->instance->config.password == NULL) { 92 | redsocks_log_error(client, LOG_NOTICE, "HTTP Proxy auth required, but no login/password configured: %s", line); 93 | client->state = httpc_no_way; 94 | } else { 95 | if (do_errtee) 96 | tee = evbuffer_new(); 97 | char *auth_request = http_auth_request_header(buffev->input, tee); 98 | if (!auth_request) { 99 | redsocks_log_error(client, LOG_NOTICE, "HTTP Proxy auth required, but no <%s> header found: %s", auth_request_header, line); 100 | client->state = httpc_no_way; 101 | } else { 102 | free(line); 103 | if (tee) 104 | evbuffer_free(tee); 105 | free(auth->last_auth_query); 106 | char *ptr = auth_request; 107 | 108 | ptr += strlen(auth_request_header); 109 | while (isspace(*ptr)) 110 | ptr++; 111 | 112 | size_t last_auth_query_len = strlen(ptr) + 1; 113 | auth->last_auth_query = calloc(last_auth_query_len, 1); 114 | memcpy(auth->last_auth_query, ptr, last_auth_query_len); 115 | auth->last_auth_count = 0; 116 | 117 | free(auth_request); 118 | 119 | if (bufferevent_disable(client->relay, EV_WRITE)) { 120 | redsocks_log_errno(client, LOG_ERR, "bufferevent_disable"); 121 | return; 122 | } 123 | 124 | /* close relay tunnel */ 125 | redsocks_bufferevent_free(client->relay); 126 | 127 | /* set to initial state*/ 128 | client->state = httpc_new; 129 | 130 | /* and reconnect */ 131 | redsocks_connect_relay(client); 132 | return; 133 | } 134 | } 135 | } else if (200 <= code && code <= 299) { 136 | client->state = httpc_reply_came; 137 | } else { 138 | redsocks_log_error(client, LOG_NOTICE, "HTTP Proxy error: %s", line); 139 | client->state = httpc_no_way; 140 | } 141 | } else { 142 | redsocks_log_error(client, LOG_NOTICE, "HTTP Proxy bad firstline: %s", line); 143 | client->state = httpc_no_way; 144 | } 145 | if (do_errtee && client->state == httpc_no_way) { 146 | if (bufferevent_write(client->client, line, strlen(line)) != 0 || 147 | bufferevent_write(client->client, "\r\n", 2) != 0) 148 | { 149 | redsocks_log_errno(client, LOG_NOTICE, "bufferevent_write"); 150 | goto fail; 151 | } 152 | } 153 | free(line); 154 | } 155 | else if (len >= HTTP_HEAD_WM_HIGH) { 156 | redsocks_log_error(client, LOG_NOTICE, "HTTP Proxy reply is too long, %zu bytes", len); 157 | client->state = httpc_no_way; 158 | } 159 | } 160 | 161 | if (do_errtee && client->state == httpc_no_way) { 162 | if (tee) { 163 | if (bufferevent_write_buffer(client->client, tee) != 0) { 164 | redsocks_log_errno(client, LOG_NOTICE, "bufferevent_write_buffer"); 165 | goto fail; 166 | } 167 | } 168 | redsocks_shutdown(client, client->client, SHUT_RD); 169 | const size_t avail = evbuffer_get_length(client->client->input); 170 | if (avail) { 171 | if (evbuffer_drain(client->client->input, avail) != 0) { 172 | redsocks_log_errno(client, LOG_NOTICE, "evbuffer_drain"); 173 | goto fail; 174 | } 175 | } 176 | redsocks_shutdown(client, client->relay, SHUT_WR); 177 | client->state = httpc_headers_skipped; 178 | } 179 | 180 | fail: 181 | if (tee) { 182 | evbuffer_free(tee); 183 | } 184 | 185 | if (client->state == httpc_no_way) { 186 | redsocks_drop_client(client); 187 | return; 188 | } 189 | 190 | while (client->state == httpc_reply_came) { 191 | char *line = redsocks_evbuffer_readline(buffev->input); 192 | if (line) { 193 | if (strlen(line) == 0) { 194 | client->state = httpc_headers_skipped; 195 | } 196 | free(line); 197 | } 198 | else { 199 | break; 200 | } 201 | } 202 | 203 | if (client->state == httpc_headers_skipped) { 204 | redsocks_start_relay(client); 205 | } 206 | } 207 | 208 | static struct evbuffer *httpc_mkconnect(redsocks_client *client) 209 | { 210 | struct evbuffer *buff = NULL, *retval = NULL; 211 | char *auth_string = NULL; 212 | int len; 213 | 214 | buff = evbuffer_new(); 215 | if (!buff) { 216 | redsocks_log_errno(client, LOG_ERR, "evbuffer_new"); 217 | goto fail; 218 | } 219 | 220 | http_auth *auth = red_http_auth(client->instance); 221 | ++auth->last_auth_count; 222 | 223 | const char *auth_scheme = NULL; 224 | 225 | if (auth->last_auth_query != NULL) { 226 | /* find previous auth challange */ 227 | 228 | if (strncasecmp(auth->last_auth_query, "Basic", 5) == 0) { 229 | auth_string = basic_authentication_encode(client->instance->config.login, client->instance->config.password); 230 | auth_scheme = "Basic"; 231 | } else if (strncasecmp(auth->last_auth_query, "Digest", 6) == 0) { 232 | /* calculate uri */ 233 | char uri[128]; 234 | snprintf(uri, 128, "%s:%u", inet_ntoa(client->destaddr.sin_addr), ntohs(client->destaddr.sin_port)); 235 | 236 | /* prepare an random string for cnounce */ 237 | char cnounce[17]; 238 | snprintf(cnounce, sizeof(cnounce), "%08x%08x", red_randui32(), red_randui32()); 239 | 240 | auth_string = digest_authentication_encode(auth->last_auth_query + 7, //line 241 | client->instance->config.login, client->instance->config.password, //user, pass 242 | "CONNECT", uri, auth->last_auth_count, cnounce); // method, path, nc, cnounce 243 | auth_scheme = "Digest"; 244 | } 245 | } 246 | 247 | // TODO: do accurate evbuffer_expand() while cleaning up http-auth 248 | len = evbuffer_add_printf(buff, "CONNECT %s:%u HTTP/1.0\r\n", 249 | inet_ntoa(client->destaddr.sin_addr), 250 | ntohs(client->destaddr.sin_port)); 251 | if (len < 0) { 252 | redsocks_log_errno(client, LOG_ERR, "evbufer_add_printf"); 253 | goto fail; 254 | } 255 | 256 | if (auth_string) { 257 | len = evbuffer_add_printf(buff, "%s %s %s\r\n", 258 | auth_response_header, auth_scheme, auth_string); 259 | if (len < 0) { 260 | redsocks_log_errno(client, LOG_ERR, "evbufer_add_printf"); 261 | goto fail; 262 | } 263 | free(auth_string); 264 | auth_string = NULL; 265 | } 266 | 267 | const enum disclose_src_e disclose_src = client->instance->config.disclose_src; 268 | if (disclose_src != DISCLOSE_NONE) { 269 | char clientip[INET_ADDRSTRLEN]; 270 | const char *ip = inet_ntop(client->clientaddr.sin_family, &client->clientaddr.sin_addr, clientip, sizeof(clientip)); 271 | if (!ip) { 272 | redsocks_log_errno(client, LOG_ERR, "inet_ntop"); 273 | goto fail; 274 | } 275 | if (disclose_src == DISCLOSE_X_FORWARDED_FOR) { 276 | len = evbuffer_add_printf(buff, "X-Forwarded-For: %s\r\n", ip); 277 | } else if (disclose_src == DISCLOSE_FORWARDED_IP) { 278 | len = evbuffer_add_printf(buff, "Forwarded: for=%s\r\n", ip); 279 | } else if (disclose_src == DISCLOSE_FORWARDED_IPPORT) { 280 | len = evbuffer_add_printf(buff, "Forwarded: for=\"%s:%d\"\r\n", ip, 281 | ntohs(client->clientaddr.sin_port)); 282 | } 283 | if (len < 0) { 284 | redsocks_log_errno(client, LOG_ERR, "evbufer_add_printf"); 285 | goto fail; 286 | } 287 | } 288 | 289 | len = evbuffer_add(buff, "\r\n", 2); 290 | if (len < 0) { 291 | redsocks_log_errno(client, LOG_ERR, "evbufer_add"); 292 | goto fail; 293 | } 294 | 295 | retval = buff; 296 | buff = NULL; 297 | 298 | fail: 299 | if (auth_string) 300 | free(auth_string); 301 | if (buff) 302 | evbuffer_free(buff); 303 | return retval; 304 | } 305 | 306 | 307 | static void httpc_write_cb(struct bufferevent *buffev, void *_arg) 308 | { 309 | redsocks_client *client = _arg; 310 | 311 | redsocks_touch_client(client); 312 | 313 | if (client->state == httpc_new) { 314 | redsocks_write_helper_ex( 315 | buffev, client, 316 | httpc_mkconnect, httpc_request_sent, 1, HTTP_HEAD_WM_HIGH 317 | ); 318 | } 319 | else if (client->state >= httpc_request_sent) { 320 | bufferevent_disable(buffev, EV_WRITE); 321 | } 322 | } 323 | 324 | 325 | relay_subsys http_connect_subsys = 326 | { 327 | .name = "http-connect", 328 | .payload_len = 0, 329 | .instance_payload_len = sizeof(http_auth), 330 | .readcb = httpc_read_cb, 331 | .writecb = httpc_write_cb, 332 | .init = httpc_client_init, 333 | .instance_fini = httpc_instance_fini, 334 | }; 335 | 336 | /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ 337 | /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ 338 | -------------------------------------------------------------------------------- /libc-compat.h: -------------------------------------------------------------------------------- 1 | #ifndef UUID_67C91670_FCCB_4855_BDF7_609F1EECB8B4 2 | #define UUID_67C91670_FCCB_4855_BDF7_609F1EECB8B4 3 | 4 | /* all these definitions, are included into bits/in.h from libc6-dev 2.15-0ubuntu10 5 | * from Ubuntu 12.04 and is not included into libc6-dev 2.11.1-0ubuntu7.10 from 6 | * Ubuntu 10.04. 7 | * linux/in.h is not included directly because of lots of redefinitions, 8 | * extracting single value from linux/in.h is not done because it looks like 9 | * autotools reinvention */ 10 | #ifndef IP_ORIGDSTADDR 11 | # warning Using hardcoded value for IP_ORIGDSTADDR as libc headers do not define it. 12 | # define IP_ORIGDSTADDR 20 13 | #endif 14 | 15 | #ifndef IP_RECVORIGDSTADDR 16 | # warning Using hardcoded value for IP_RECVORIGDSTADDR as libc headers do not define it. 17 | # define IP_RECVORIGDSTADDR IP_ORIGDSTADDR 18 | #endif 19 | 20 | #ifndef IP_TRANSPARENT 21 | # warning Using hardcoded value for IP_TRANSPARENT as libc headers do not define it. 22 | # define IP_TRANSPARENT 19 23 | #endif 24 | 25 | #endif // 67C91670_FCCB_4855_BDF7_609F1EECB8B4 26 | -------------------------------------------------------------------------------- /libevent-compat.h: -------------------------------------------------------------------------------- 1 | #ifndef UUID_FC148CFA_5ECC_488E_8A62_CD39406C9F1E 2 | #define UUID_FC148CFA_5ECC_488E_8A62_CD39406C9F1E 3 | 4 | /* evutil_socket_t is macros in libevent-2.0, not typedef, libevent-1.4 is 5 | * still supported because of Ubuntu 10.04 LTS */ 6 | #ifndef evutil_socket_t 7 | # warning Using hardcoded value for evutil_socket_t as libevent headers do not define it. 8 | # define evutil_socket_t int 9 | #endif 10 | 11 | #endif // FC148CFA_5ECC_488E_8A62_CD39406C9F1E 12 | -------------------------------------------------------------------------------- /log.c: -------------------------------------------------------------------------------- 1 | /* redsocks - transparent TCP-to-proxy redirector 2 | * Copyright (C) 2007-2018 Leonid Evdokimov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include "utils.h" 24 | #include "log.h" 25 | 26 | const char *error_lowmem = ""; 27 | 28 | typedef void (*log_func)(const char *file, int line, const char *func, int priority, const char *message, const char *appendix); 29 | 30 | static const char* getprioname(int priority) 31 | { 32 | switch (priority) { 33 | case LOG_EMERG: return "emerg"; 34 | case LOG_ALERT: return "alert"; 35 | case LOG_CRIT: return "crit"; 36 | case LOG_ERR: return "err"; 37 | case LOG_WARNING: return "warning"; 38 | case LOG_NOTICE: return "notice"; 39 | case LOG_INFO: return "info"; 40 | case LOG_DEBUG: return "debug"; 41 | default: return "?"; 42 | } 43 | } 44 | 45 | static void fprint_timestamp( 46 | FILE* fd, 47 | const char *file, int line, const char *func, int priority, const char *message, const char *appendix) 48 | { 49 | struct timeval tv = { }; 50 | gettimeofday(&tv, 0); 51 | 52 | /* XXX: there is no error-checking, IMHO it's better to lose messages 53 | * then to die and stop service */ 54 | const char* sprio = getprioname(priority); 55 | if (appendix) 56 | fprintf(fd, "%lu.%6.6lu %s %s:%u %s(...) %s: %s\n", tv.tv_sec, tv.tv_usec, sprio, file, line, func, message, appendix); 57 | else 58 | fprintf(fd, "%lu.%6.6lu %s %s:%u %s(...) %s\n", tv.tv_sec, tv.tv_usec, sprio, file, line, func, message); 59 | } 60 | 61 | static void stderr_msg(const char *file, int line, const char *func, int priority, const char *message, const char *appendix) 62 | { 63 | fprint_timestamp(stderr, file, line, func, priority, message, appendix); 64 | } 65 | 66 | static FILE *logfile = NULL; 67 | 68 | static void logfile_msg(const char *file, int line, const char *func, int priority, const char *message, const char *appendix) 69 | { 70 | fprint_timestamp(logfile, file, line, func, priority, message, appendix); 71 | fflush(logfile); 72 | } 73 | 74 | static void syslog_msg(const char *file, int line, const char *func, int priority, const char *message, const char *appendix) 75 | { 76 | if (appendix) 77 | syslog(priority, "%s: %s\n", message, appendix); 78 | else 79 | syslog(priority, "%s\n", message); 80 | } 81 | 82 | static log_func log_msg = stderr_msg; 83 | static log_func log_msg_next = NULL; 84 | static bool should_log_info = true; 85 | static bool should_log_debug = false; 86 | 87 | bool should_log(int priority) 88 | { 89 | return (priority != LOG_DEBUG && priority != LOG_INFO) 90 | || (priority == LOG_DEBUG && should_log_debug) 91 | || (priority == LOG_INFO && should_log_info); 92 | } 93 | 94 | int log_preopen(const char *dst, bool log_debug, bool log_info) 95 | { 96 | const char *syslog_prefix = "syslog:"; 97 | const char *file_prefix = "file:"; 98 | should_log_debug = log_debug; 99 | should_log_info = log_info; 100 | if (strcmp(dst, "stderr") == 0) { 101 | log_msg_next = stderr_msg; 102 | } 103 | else if (strncmp(dst, syslog_prefix, strlen(syslog_prefix)) == 0) { 104 | const char *facility_name = dst + strlen(syslog_prefix); 105 | int facility = -1; 106 | int logmask; 107 | struct { 108 | char *name; int value; 109 | } *ptpl, tpl[] = { 110 | { "daemon", LOG_DAEMON }, 111 | { "local0", LOG_LOCAL0 }, 112 | { "local1", LOG_LOCAL1 }, 113 | { "local2", LOG_LOCAL2 }, 114 | { "local3", LOG_LOCAL3 }, 115 | { "local4", LOG_LOCAL4 }, 116 | { "local5", LOG_LOCAL5 }, 117 | { "local6", LOG_LOCAL6 }, 118 | { "local7", LOG_LOCAL7 }, 119 | }; 120 | 121 | FOREACH(ptpl, tpl) 122 | if (strcmp(facility_name, ptpl->name) == 0) { 123 | facility = ptpl->value; 124 | break; 125 | } 126 | if (facility == -1) { 127 | log_error(LOG_ERR, "log_preopen(%s, ...): unknown syslog facility", dst); 128 | return -1; 129 | } 130 | 131 | openlog("redsocks", LOG_NDELAY | LOG_PID, facility); 132 | 133 | logmask = setlogmask(0); 134 | if (!log_debug) 135 | logmask &= ~(LOG_MASK(LOG_DEBUG)); 136 | if (!log_info) 137 | logmask &= ~(LOG_MASK(LOG_INFO)); 138 | setlogmask(logmask); 139 | 140 | log_msg_next = syslog_msg; 141 | } 142 | else if (strncmp(dst, file_prefix, strlen(file_prefix)) == 0) { 143 | const char *filename = dst + strlen(file_prefix); 144 | if ((logfile = fopen(filename, "a")) == NULL) { 145 | log_error(LOG_ERR, "log_preopen(%s, ...): %s", dst, strerror(errno)); 146 | return -1; 147 | } 148 | log_msg_next = logfile_msg; 149 | /* TODO: add log rotation */ 150 | } 151 | else { 152 | log_error(LOG_ERR, "log_preopen(%s, ...): unknown destination", dst); 153 | return -1; 154 | } 155 | return 0; 156 | } 157 | 158 | void log_open() 159 | { 160 | log_msg = log_msg_next; 161 | log_msg_next = NULL; 162 | } 163 | 164 | void _log_vwrite(const char *file, int line, const char *func, int do_errno, int priority, const char *fmt, va_list ap) 165 | { 166 | if (!should_log(priority)) 167 | return; 168 | 169 | int saved_errno = errno; 170 | struct evbuffer *buff = evbuffer_new(); 171 | const char *message; 172 | 173 | if (buff) { 174 | evbuffer_add_vprintf(buff, fmt, ap); 175 | message = (const char*)evbuffer_pullup(buff, -1); 176 | } 177 | else 178 | message = error_lowmem; 179 | 180 | log_msg(file, line, func, priority, message, do_errno ? strerror(saved_errno) : NULL); 181 | 182 | if (buff) 183 | evbuffer_free(buff); 184 | } 185 | 186 | void _log_write(const char *file, int line, const char *func, int do_errno, int priority, const char *fmt, ...) 187 | { 188 | if (!should_log(priority)) 189 | return; 190 | 191 | va_list ap; 192 | 193 | va_start(ap, fmt); 194 | _log_vwrite(file, line, func, do_errno, priority, fmt, ap); 195 | va_end(ap); 196 | } 197 | 198 | /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ 199 | /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ 200 | -------------------------------------------------------------------------------- /log.h: -------------------------------------------------------------------------------- 1 | #ifndef LOG_H_WED_JAN_24_18_21_27_2007 2 | #define LOG_H_WED_JAN_24_18_21_27_2007 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #define log_errno(prio, msg...) _log_write(__FILE__, __LINE__, __func__, 1, prio, ## msg) 9 | #define log_error(prio, msg...) _log_write(__FILE__, __LINE__, __func__, 0, prio, ## msg) 10 | 11 | extern const char *error_lowmem; 12 | 13 | int log_preopen(const char *dst, bool log_debug, bool log_info); 14 | void log_open(); 15 | 16 | bool should_log(int priority); 17 | 18 | void _log_vwrite(const char *file, int line, const char *func, int do_errno, int priority, const char *fmt, va_list ap); 19 | 20 | void _log_write(const char *file, int line, const char *func, int do_errno, int priority, const char *fmt, ...) 21 | #if defined(__GNUC__) 22 | __attribute__ (( format (printf, 6, 7) )) 23 | #endif 24 | ; 25 | 26 | /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ 27 | /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ 28 | #endif /* LOG_H_WED_JAN_24_18_21_27_2007 */ 29 | 30 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | /* redsocks - transparent TCP-to-proxy redirector 2 | * Copyright (C) 2007-2018 Leonid Evdokimov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include "log.h" 26 | #include "main.h" 27 | #include "utils.h" 28 | #include "version.h" 29 | #include "config.h" 30 | #include "base.h" 31 | 32 | extern app_subsys redsocks_subsys; 33 | extern app_subsys debug_subsys; 34 | extern app_subsys base_subsys; 35 | extern app_subsys redudp_subsys; 36 | extern app_subsys dnstc_subsys; 37 | extern app_subsys dnsu2t_subsys; 38 | 39 | app_subsys *subsystems[] = { 40 | &redsocks_subsys, 41 | #ifdef DBG_BUILD 42 | &debug_subsys, 43 | #endif 44 | &base_subsys, 45 | &redudp_subsys, 46 | &dnstc_subsys, 47 | &dnsu2t_subsys, 48 | }; 49 | 50 | static const char *confname = "redsocks.conf"; 51 | static const char *pidfile = NULL; 52 | 53 | static void terminate(int sig, short what, void *_arg) 54 | { 55 | if (event_loopbreak() != 0) 56 | log_error(LOG_WARNING, "event_loopbreak"); 57 | } 58 | 59 | int main(int argc, char **argv) 60 | { 61 | int error; 62 | app_subsys **ss; 63 | int exit_signals[2] = {SIGTERM, SIGINT}; 64 | struct event terminators[2]; 65 | bool conftest = false; 66 | int opt; 67 | int i; 68 | 69 | evutil_secure_rng_init(); 70 | while ((opt = getopt(argc, argv, "h?vtc:p:")) != -1) { 71 | switch (opt) { 72 | case 't': 73 | conftest = true; 74 | break; 75 | case 'c': 76 | confname = optarg; 77 | break; 78 | case 'p': 79 | pidfile = optarg; 80 | break; 81 | case 'v': 82 | puts(redsocks_version); 83 | printf("Built with libevent-%s\n", LIBEVENT_VERSION); 84 | printf("Runs with libevent-%s\n", event_get_version()); 85 | if (LIBEVENT_VERSION_NUMBER != event_get_version_number()) { 86 | printf("Warning: libevent version number mismatch.\n" 87 | " Headers: %8x\n" 88 | " Runtime: %8x\n", LIBEVENT_VERSION_NUMBER, event_get_version_number()); 89 | } 90 | return EXIT_SUCCESS; 91 | default: 92 | printf( 93 | "Usage: %s [-?hvt] [-c config] [-p pidfile]\n" 94 | " -h, -? this message\n" 95 | " -v print version\n" 96 | " -t test config syntax\n" 97 | " -p write pid to pidfile\n", 98 | argv[0]); 99 | return (opt == '?' || opt == 'h') ? EXIT_SUCCESS : EXIT_FAILURE; 100 | } 101 | } 102 | 103 | if (event_get_struct_event_size() != sizeof(struct event)) { 104 | puts("libevent event_get_struct_event_size() != sizeof(struct event)! Check `redsocks -v` and recompile redsocks"); 105 | return EXIT_FAILURE; 106 | } 107 | 108 | FILE *f = fopen(confname, "r"); 109 | if (!f) { 110 | perror("Unable to open config file"); 111 | return EXIT_FAILURE; 112 | } 113 | 114 | parser_context* parser = parser_start(f); 115 | if (!parser) { 116 | perror("Not enough memory for parser"); 117 | return EXIT_FAILURE; 118 | } 119 | 120 | FOREACH(ss, subsystems) 121 | if ((*ss)->conf_section) 122 | parser_add_section(parser, (*ss)->conf_section); 123 | error = parser_run(parser); 124 | parser_stop(parser); 125 | fclose(f); 126 | 127 | if (error) 128 | return EXIT_FAILURE; 129 | 130 | if (conftest) 131 | return EXIT_SUCCESS; 132 | 133 | struct event_base* evbase = event_init(); 134 | memset(terminators, 0, sizeof(terminators)); 135 | 136 | FOREACH(ss, subsystems) { 137 | if ((*ss)->init) { 138 | error = (*ss)->init(evbase); 139 | if (error) 140 | goto shutdown; 141 | } 142 | } 143 | 144 | if (pidfile) { 145 | f = fopen(pidfile, "w"); 146 | if (!f) { 147 | perror("Unable to open pidfile for write"); 148 | return EXIT_FAILURE; 149 | } 150 | fprintf(f, "%d\n", getpid()); 151 | fclose(f); 152 | } 153 | 154 | assert(SIZEOF_ARRAY(exit_signals) == SIZEOF_ARRAY(terminators)); 155 | for (i = 0; i < SIZEOF_ARRAY(exit_signals); i++) { 156 | signal_set(&terminators[i], exit_signals[i], terminate, NULL); 157 | if (signal_add(&terminators[i], NULL) != 0) { 158 | log_errno(LOG_ERR, "signal_add"); 159 | goto shutdown; 160 | } 161 | } 162 | 163 | if (LIBEVENT_VERSION_NUMBER != event_get_version_number()) { 164 | log_error(LOG_WARNING, "libevent version mismatch! headers %8x, runtime %8x\n", LIBEVENT_VERSION_NUMBER, event_get_version_number()); 165 | } 166 | 167 | log_error(LOG_NOTICE, "redsocks started, conn_max=%u", redsocks_conn_max()); 168 | 169 | event_dispatch(); 170 | 171 | log_error(LOG_NOTICE, "redsocks goes down"); 172 | 173 | shutdown: 174 | for (i = 0; i < SIZEOF_ARRAY(exit_signals); i++) { 175 | if (signal_initialized(&terminators[i])) { 176 | if (signal_del(&terminators[i]) != 0) 177 | log_errno(LOG_WARNING, "signal_del"); 178 | memset(&terminators[i], 0, sizeof(terminators[i])); 179 | } 180 | } 181 | 182 | for (--ss; ss >= subsystems; ss--) 183 | if ((*ss)->fini) 184 | (*ss)->fini(); 185 | 186 | event_base_free(evbase); 187 | 188 | return !error ? EXIT_SUCCESS : EXIT_FAILURE; 189 | } 190 | 191 | /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ 192 | /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ 193 | -------------------------------------------------------------------------------- /main.h: -------------------------------------------------------------------------------- 1 | #ifndef MAIN_H_TUE_JAN_23_15_38_25_2007 2 | #define MAIN_H_TUE_JAN_23_15_38_25_2007 3 | 4 | #include "parser.h" 5 | 6 | struct event_base; 7 | 8 | typedef struct app_subsys_t { 9 | int (*init)(struct event_base*); 10 | int (*fini)(); 11 | parser_section* conf_section; 12 | } app_subsys; 13 | 14 | #define SIZEOF_ARRAY(arr) (sizeof(arr) / sizeof(arr[0])) 15 | #define FOREACH(ptr, array) for (ptr = array; ptr < array + SIZEOF_ARRAY(array); ptr++) 16 | #define FOREACH_REV(ptr, array) for (ptr = array + SIZEOF_ARRAY(array) - 1; ptr >= array; ptr--) 17 | 18 | 19 | /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ 20 | /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ 21 | #endif /* MAIN_H_TUE_JAN_23_15_38_25_2007 */ 22 | -------------------------------------------------------------------------------- /md5.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 1999, 2002 Aladdin Enterprises. All rights reserved. 3 | 4 | This software is provided 'as-is', without any express or implied 5 | warranty. In no event will the authors be held liable for any damages 6 | arising from the use of this software. 7 | 8 | Permission is granted to anyone to use this software for any purpose, 9 | including commercial applications, and to alter it and redistribute it 10 | freely, subject to the following restrictions: 11 | 12 | 1. The origin of this software must not be misrepresented; you must not 13 | claim that you wrote the original software. If you use this software 14 | in a product, an acknowledgment in the product documentation would be 15 | appreciated but is not required. 16 | 2. Altered source versions must be plainly marked as such, and must not be 17 | misrepresented as being the original software. 18 | 3. This notice may not be removed or altered from any source distribution. 19 | 20 | L. Peter Deutsch 21 | ghost@aladdin.com 22 | 23 | */ 24 | /* $Id: md5.h,v 1.4 2002/04/13 19:20:28 lpd Exp $ */ 25 | /* 26 | Independent implementation of MD5 (RFC 1321). 27 | 28 | This code implements the MD5 Algorithm defined in RFC 1321, whose 29 | text is available at 30 | http://www.ietf.org/rfc/rfc1321.txt 31 | The code is derived from the text of the RFC, including the test suite 32 | (section A.5) but excluding the rest of Appendix A. It does not include 33 | any code or documentation that is identified in the RFC as being 34 | copyrighted. 35 | 36 | The original and principal author of md5.h is L. Peter Deutsch 37 | . Other authors are noted in the change history 38 | that follows (in reverse chronological order): 39 | 40 | 2002-04-13 lpd Removed support for non-ANSI compilers; removed 41 | references to Ghostscript; clarified derivation from RFC 1321; 42 | now handles byte order either statically or dynamically. 43 | 1999-11-04 lpd Edited comments slightly for automatic TOC extraction. 44 | 1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5); 45 | added conditionalization for C++ compilation from Martin 46 | Purschke . 47 | 1999-05-03 lpd Original version. 48 | */ 49 | 50 | #ifndef md5_INCLUDED 51 | # define md5_INCLUDED 52 | 53 | /* 54 | * This package supports both compile-time and run-time determination of CPU 55 | * byte order. If ARCH_IS_BIG_ENDIAN is defined as 0, the code will be 56 | * compiled to run only on little-endian CPUs; if ARCH_IS_BIG_ENDIAN is 57 | * defined as non-zero, the code will be compiled to run only on big-endian 58 | * CPUs; if ARCH_IS_BIG_ENDIAN is not defined, the code will be compiled to 59 | * run on either big- or little-endian CPUs, but will run slightly less 60 | * efficiently on either one than if ARCH_IS_BIG_ENDIAN is defined. 61 | */ 62 | 63 | typedef unsigned char md5_byte_t; /* 8-bit byte */ 64 | typedef unsigned int md5_word_t; /* 32-bit word */ 65 | 66 | /* Define the state of the MD5 Algorithm. */ 67 | typedef struct md5_state_s { 68 | md5_word_t count[2]; /* message length in bits, lsw first */ 69 | md5_word_t abcd[4]; /* digest buffer */ 70 | md5_byte_t buf[64]; /* accumulate block */ 71 | } md5_state_t; 72 | 73 | #ifdef __cplusplus 74 | extern "C" 75 | { 76 | #endif 77 | 78 | /* Initialize the algorithm. */ 79 | void md5_init(md5_state_t *pms); 80 | 81 | /* Append a string to the message. */ 82 | void md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes); 83 | 84 | /* Finish the message and return the digest. */ 85 | void md5_finish(md5_state_t *pms, md5_byte_t digest[16]); 86 | 87 | #ifdef __cplusplus 88 | } /* end extern "C" */ 89 | #endif 90 | 91 | #endif /* md5_INCLUDED */ 92 | -------------------------------------------------------------------------------- /parser.h: -------------------------------------------------------------------------------- 1 | #ifndef PARSER_H_THU_JAN_11_04_49_38_2007 2 | #define PARSER_H_THU_JAN_11_04_49_38_2007 3 | 4 | #include 5 | #include 6 | 7 | enum disclose_src_e { 8 | DISCLOSE_NONE, 9 | DISCLOSE_X_FORWARDED_FOR, 10 | DISCLOSE_FORWARDED_IP, 11 | DISCLOSE_FORWARDED_IPPORT, 12 | }; 13 | 14 | enum on_proxy_fail_e { 15 | ONFAIL_CLOSE, 16 | ONFAIL_FORWARD_HTTP_ERR, 17 | }; 18 | 19 | typedef enum { 20 | pt_bool, // "bool" from stdbool.h, not "_Bool" or anything else 21 | pt_pchar, 22 | pt_uint16, 23 | pt_uint32, 24 | pt_in_addr, 25 | pt_in_addr2, // inaddr[0] = net, inaddr[1] = netmask 26 | pt_disclose_src, 27 | pt_on_proxy_fail, 28 | pt_obsolete, 29 | pt_redsocks_max_accept_backoff, 30 | } parser_type; 31 | 32 | typedef struct parser_entry_t { 33 | const char *key; 34 | parser_type type; 35 | void *addr; 36 | } parser_entry; 37 | 38 | 39 | typedef struct parser_context_t parser_context; 40 | 41 | 42 | typedef struct parser_section_t parser_section; 43 | typedef int (*parser_section_onenter)(parser_section *section); 44 | typedef int (*parser_section_onexit)(parser_section *section); 45 | 46 | struct parser_section_t { 47 | parser_section *next; 48 | parser_context *context; 49 | const char *name; 50 | parser_section_onenter onenter; // is called on entry to section 51 | parser_section_onexit onexit; // is called on exit from section 52 | parser_entry *entries; 53 | void *data; 54 | }; 55 | 56 | 57 | 58 | parser_context* parser_start(FILE *fd); 59 | void parser_add_section(parser_context *context, parser_section *section); 60 | int parser_run(parser_context *context); 61 | void parser_error(parser_context *context, const char *fmt, ...) 62 | #if defined(__GNUC__) 63 | __attribute__ (( format (printf, 2, 3) )) 64 | #endif 65 | ; 66 | void parser_stop(parser_context *context); 67 | 68 | /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ 69 | /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ 70 | #endif /* PARSER_H_THU_JAN_11_04_49_38_2007 */ 71 | -------------------------------------------------------------------------------- /redsocks.conf.example: -------------------------------------------------------------------------------- 1 | base { 2 | // debug: connection progress 3 | log_debug = off; 4 | 5 | // info: start and end of client session 6 | log_info = on; 7 | 8 | /* possible `log' values are: 9 | * stderr 10 | * "file:/path/to/file" 11 | * syslog:FACILITY facility is any of "daemon", "local0"..."local7" 12 | */ 13 | log = stderr; 14 | // log = "file:/path/to/file"; 15 | // log = "syslog:local7"; 16 | 17 | // detach from console 18 | daemon = off; 19 | 20 | /* Change uid, gid and root directory, these options require root 21 | * privilegies on startup. 22 | * Note, your chroot may requre /etc/localtime if you write log to syslog. 23 | * Log is opened before chroot & uid changing. 24 | * Debian, Ubuntu and some other distributions use `nogroup` instead of 25 | * `nobody`, so change it according to your system if you want redsocks 26 | * to drop root privileges. 27 | */ 28 | // user = nobody; 29 | // group = nobody; 30 | // chroot = "/var/chroot"; 31 | 32 | /* possible `redirector' values are: 33 | * iptables - for Linux 34 | * ipf - for FreeBSD 35 | * pf - for OpenBSD 36 | * generic - some generic redirector that MAY work 37 | */ 38 | redirector = iptables; 39 | 40 | /* Override per-socket values for TCP_KEEPIDLE, TCP_KEEPCNT, 41 | * and TCP_KEEPINTVL. see man 7 tcp for details. 42 | * `redsocks' relies on SO_KEEPALIVE option heavily. */ 43 | //tcp_keepalive_time = 0; 44 | //tcp_keepalive_probes = 0; 45 | //tcp_keepalive_intvl = 0; 46 | 47 | // Every `redsocks` connection needs two file descriptors for sockets. 48 | // If `splice` is enabled, it also needs four file descriptors for 49 | // pipes. `redudp` is not accounted at the moment. When max number of 50 | // connection is reached, redsocks tries to close idle connections. If 51 | // there are no idle connections, it stops accept()'ing new 52 | // connections, although kernel continues to fill listenq. 53 | 54 | // Set maximum number of open file descriptors (also known as `ulimit -n`). 55 | // 0 -- do not modify startup limit (default) 56 | // rlimit_nofile = 0; 57 | 58 | // Set maximum number of served connections. Default is to deduce safe 59 | // limit from `splice` setting and RLIMIT_NOFILE. 60 | // redsocks_conn_max = 0; 61 | 62 | // Close connections idle for N seconds when/if connection count 63 | // limit is hit. 64 | // 0 -- do not close idle connections 65 | // 7440 -- 2 hours 4 minutes, see RFC 5382 (default) 66 | // connpres_idle_timeout = 7440; 67 | 68 | // `max_accept_backoff` is a delay in milliseconds to retry `accept()` 69 | // after failure (e.g. due to lack of file descriptors). It's just a 70 | // safety net for misconfigured `redsocks_conn_max`, you should tune 71 | // redsocks_conn_max if accept backoff happens. 72 | // max_accept_backoff = 60000; 73 | } 74 | 75 | redsocks { 76 | /* `local_ip' defaults to 127.0.0.1 for security reasons, 77 | * use 0.0.0.0 if you want to listen on every interface. 78 | * `local_*' are used as port to redirect to. 79 | */ 80 | local_ip = 127.0.0.1; 81 | local_port = 12345; 82 | 83 | // listen() queue length. Default value is SOMAXCONN and it should be 84 | // good enough for most of us. 85 | // listenq = 128; // SOMAXCONN equals 128 on my Linux box. 86 | 87 | // Enable or disable faster data pump based on splice(2) syscall. 88 | // Default value depends on your kernel version, true for 2.6.27.13+ 89 | // splice = false; 90 | 91 | // `ip' and `port' are IP and tcp-port of proxy-server 92 | // You can also use hostname instead of IP, only one (random) 93 | // address of multihomed host will be used. 94 | ip = example.org; 95 | port = 1080; 96 | 97 | // known types: socks4, socks5, http-connect, http-relay 98 | type = socks5; 99 | 100 | // login = "foobar"; 101 | // password = "baz"; 102 | 103 | // known ways to disclose client IP to the proxy: 104 | // false -- disclose nothing 105 | // http-connect supports: 106 | // X-Forwarded-For -- X-Forwarded-For: IP 107 | // Forwarded_ip -- Forwarded: for=IP # see RFC7239 108 | // Forwarded_ipport -- Forwarded: for="IP:port" # see RFC7239 109 | // disclose_src = false; 110 | 111 | // various ways to handle proxy failure 112 | // close -- just close connection (default) 113 | // forward_http_err -- forward HTTP error page from proxy as-is 114 | // on_proxy_fail = close; 115 | } 116 | 117 | redudp { 118 | // `local_ip' should not be 0.0.0.0 as it's also used for outgoing 119 | // packets that are sent as replies - and it should be fixed 120 | // if we want NAT to work properly. 121 | local_ip = 127.0.0.1; 122 | local_port = 10053; 123 | 124 | // `ip' and `port' of socks5 proxy server. 125 | ip = 10.0.0.1; 126 | port = 1080; 127 | login = username; 128 | password = pazzw0rd; 129 | 130 | // redsocks knows about two options while redirecting UDP packets at 131 | // linux: TPROXY and REDIRECT. TPROXY requires more complex routing 132 | // configuration and fresh kernel (>= 2.6.37 according to squid 133 | // developers[1]) but has hack-free way to get original destination 134 | // address, REDIRECT is easier to configure, but requires `dest_ip` and 135 | // `dest_port` to be set, limiting packet redirection to single 136 | // destination. 137 | // [1] http://wiki.squid-cache.org/Features/Tproxy4 138 | dest_ip = 8.8.8.8; 139 | dest_port = 53; 140 | 141 | udp_timeout = 30; 142 | udp_timeout_stream = 180; 143 | } 144 | 145 | dnstc { 146 | // fake and really dumb DNS server that returns "truncated answer" to 147 | // every query via UDP, RFC-compliant resolver should repeat same query 148 | // via TCP in this case. 149 | local_ip = 127.0.0.1; 150 | local_port = 5300; 151 | } 152 | 153 | dnsu2t { 154 | // fake and a bit less dumb DNS server that converts several UDP 155 | // queries into single pipelined TCP stream of DNS queries. 156 | local_ip = 127.0.0.1; 157 | local_port = 5313; 158 | 159 | // See https://en.wikipedia.org/wiki/Public_recursive_name_server 160 | // NB: TCP connection to this ${ip}:${port} is not passed through 161 | // proxy, configure your firewall rules if you want that. 162 | remote_ip = 8.8.8.8; 163 | remote_port = 53; 164 | 165 | // Maximum amount of concurrent in-flight DNS queries sent to remote server. 166 | // Some public DNS servers seem to limit it and terminate connections 167 | // with high count of in-flight requests, so it's trade-off between 168 | // request latency and availability. In-flight requests are not cached, 169 | // so they're lost in case of DNS/TCP connection termination. 170 | // inflight_max = 16; 171 | 172 | // I/O timeout of remote endpoint. Default value is quite conservative and 173 | // corresponds to the highest timeout among public servers from Wikipedia. 174 | // remote_timeout = 30; 175 | } 176 | 177 | // you can add more `redsocks' and `redudp' sections if you need. 178 | -------------------------------------------------------------------------------- /redsocks.h: -------------------------------------------------------------------------------- 1 | #ifndef REDSOCKS_H_WED_JAN_24_22_17_11_2007 2 | #define REDSOCKS_H_WED_JAN_24_22_17_11_2007 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "list.h" 9 | #include "parser.h" 10 | #include "apple.h" 11 | 12 | struct redsocks_client_t; 13 | struct redsocks_instance_t; 14 | 15 | typedef struct relay_subsys_t { 16 | char *name; 17 | size_t payload_len; // size of relay-specific data in client section 18 | size_t instance_payload_len; // size of relay-specify data in instance section 19 | evbuffercb readcb; 20 | evbuffercb writecb; 21 | void (*init)(struct redsocks_client_t *client); 22 | void (*fini)(struct redsocks_client_t *client); 23 | void (*instance_init)(struct redsocks_instance_t *instance); 24 | void (*instance_fini)(struct redsocks_instance_t *instance); 25 | // connect_relay (if any) is called instead of redsocks_connect_relay after client connection acceptance 26 | void (*connect_relay)(struct redsocks_client_t *client); 27 | } relay_subsys; 28 | 29 | typedef struct redsocks_config_t { 30 | struct sockaddr_in bindaddr; 31 | struct sockaddr_in relayaddr; 32 | char *type; 33 | char *login; 34 | char *password; 35 | uint16_t listenq; 36 | bool use_splice; 37 | enum disclose_src_e disclose_src; 38 | enum on_proxy_fail_e on_proxy_fail; 39 | } redsocks_config; 40 | 41 | typedef struct redsocks_instance_t { 42 | list_head list; 43 | redsocks_config config; 44 | struct event listener; 45 | list_head clients; 46 | relay_subsys *relay_ss; 47 | } redsocks_instance; 48 | 49 | typedef unsigned short evshut_t; // EV_READ | EV_WRITE 50 | 51 | typedef struct redsocks_client_t { 52 | list_head list; 53 | redsocks_instance *instance; 54 | struct bufferevent *client; 55 | struct bufferevent *relay; 56 | struct sockaddr_in clientaddr; 57 | struct sockaddr_in destaddr; 58 | int state; // it's used by bottom layer 59 | evshut_t client_evshut; 60 | evshut_t relay_evshut; 61 | struct timeval first_event; 62 | struct timeval last_event; 63 | } redsocks_client; 64 | 65 | typedef struct splice_pipe_t { 66 | int read; 67 | int write; 68 | size_t size; 69 | } splice_pipe; 70 | 71 | typedef struct redsocks_pump_t { 72 | /* Quick-n-dirty test show, that some Linux 4.4.0 build uses ~1.5 kb of 73 | * slab_unreclaimable RAM per every pipe pair. Most of connections are 74 | * usually idle and it's possble to save some measurable amount of RAM 75 | * using shared pipe pool. */ 76 | redsocks_client c; 77 | splice_pipe request; 78 | splice_pipe reply; 79 | struct event client_read; 80 | struct event client_write; 81 | struct event relay_read; 82 | struct event relay_write; 83 | } redsocks_pump; 84 | 85 | static inline size_t sizeof_client(redsocks_instance *i) 86 | { 87 | return ((i->config.use_splice) ? sizeof(redsocks_pump) : sizeof(redsocks_client)) + i->relay_ss->payload_len; 88 | } 89 | 90 | static inline void* red_payload(redsocks_client *c) 91 | { 92 | return (c->instance->config.use_splice) ? (void*)(((redsocks_pump*)c) + 1) : (void*)(c + 1); 93 | } 94 | 95 | static inline redsocks_pump* red_pump(redsocks_client *c) 96 | { 97 | assert(c->instance->config.use_splice); 98 | return (redsocks_pump*)c; 99 | } 100 | 101 | void redsocks_shutdown(redsocks_client *client, struct bufferevent *buffev, int how); 102 | void redsocks_drop_client(redsocks_client *client); 103 | void redsocks_touch_client(redsocks_client *client); 104 | void redsocks_connect_relay(redsocks_client *client); 105 | void redsocks_start_relay(redsocks_client *client); 106 | bool redsocks_has_splice_instance(); 107 | 108 | typedef int (*size_comparator)(size_t a, size_t b); 109 | int sizes_equal(size_t a, size_t b); 110 | int sizes_greater_equal(size_t a, size_t b); 111 | /** helper for functions when we expect ONLY reply of some size and anything else is error 112 | */ 113 | int redsocks_read_expected(redsocks_client *client, struct evbuffer *input, void *data, size_comparator comparator, size_t expected); 114 | 115 | typedef struct evbuffer* (*redsocks_message_maker)(redsocks_client *client); 116 | typedef struct evbuffer* (*redsocks_message_maker_plain)(void *p); 117 | struct evbuffer *mkevbuffer(void *data, size_t len); 118 | /* Yahoo! This code is ex-plain! :-D */ 119 | int redsocks_write_helper_ex_plain( 120 | struct bufferevent *buffev, redsocks_client *client, 121 | redsocks_message_maker_plain mkmessage, void *p, int state, size_t wm_low, size_t wm_high); 122 | int redsocks_write_helper_ex( 123 | struct bufferevent *buffev, redsocks_client *client, 124 | redsocks_message_maker mkmessage, int state, size_t wm_low, size_t wm_high); 125 | int redsocks_write_helper( 126 | struct bufferevent *buffev, redsocks_client *client, 127 | redsocks_message_maker mkmessage, int state, size_t wm_only); 128 | 129 | 130 | #define redsocks_close(fd) redsocks_close_internal((fd), __FILE__, __LINE__, __func__) 131 | void redsocks_close_internal(int fd, const char* file, int line, const char *func); 132 | 133 | #define redsocks_event_add(client, ev) redsocks_event_add_internal((client), (ev), __FILE__, __LINE__, __func__) 134 | void redsocks_event_add_internal(redsocks_client *client, struct event *ev, const char *file, int line, const char *func); 135 | 136 | #define redsocks_event_del(client, ev) redsocks_event_del_internal((client), (ev), __FILE__, __LINE__, __func__) 137 | void redsocks_event_del_internal(redsocks_client *client, struct event *ev, const char *file, int line, const char *func); 138 | 139 | #define redsocks_bufferevent_dropfd(client, ev) redsocks_bufferevent_dropfd_internal((client), (ev), __FILE__, __LINE__, __func__) 140 | void redsocks_bufferevent_dropfd_internal(redsocks_client *client, struct bufferevent *ev, const char *file, int line, const char *func); 141 | 142 | // I have to account descriptiors for accept-backoff, that's why BEV_OPT_CLOSE_ON_FREE is not used. 143 | void redsocks_bufferevent_free(struct bufferevent *buffev); 144 | 145 | #define redsocks_log_error(client, prio, msg...) \ 146 | redsocks_log_write_plain(__FILE__, __LINE__, __func__, 0, &(client)->clientaddr, &(client)->destaddr, prio, ## msg) 147 | #define redsocks_log_errno(client, prio, msg...) \ 148 | redsocks_log_write_plain(__FILE__, __LINE__, __func__, 1, &(client)->clientaddr, &(client)->destaddr, prio, ## msg) 149 | void redsocks_log_write_plain( 150 | const char *file, int line, const char *func, int do_errno, 151 | const struct sockaddr_in *clientaddr, const struct sockaddr_in *destaddr, 152 | int priority, const char *fmt, ...) 153 | #if defined(__GNUC__) 154 | __attribute__ (( format (printf, 8, 9) )) 155 | #endif 156 | ; 157 | 158 | /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ 159 | /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ 160 | #endif /* REDSOCKS_H_WED_JAN_24_22_17_11_2007 */ 161 | 162 | -------------------------------------------------------------------------------- /redsocks.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Transparent redirector of any TCP connection to proxy using your firewall 3 | 4 | [Service] 5 | Type=forking 6 | PIDFile=/run/redsocks/redsocks.pid 7 | EnvironmentFile=/etc/conf.d/redsocks 8 | User=redsocks 9 | ExecStartPre=/usr/bin/redsocks -t -c $REDSOCKS_CONF 10 | ExecStart=/usr/bin/redsocks -c $REDSOCKS_CONF \ 11 | -p /run/redsocks/redsocks.pid 12 | ExecStopPost=/bin/rm /run/redsocks/redsocks.pid 13 | Restart=on-abort 14 | 15 | [Install] 16 | WantedBy=multi-user.target 17 | -------------------------------------------------------------------------------- /redudp.h: -------------------------------------------------------------------------------- 1 | #ifndef REDUDP_H 2 | #define REDUDP_H 3 | 4 | typedef struct redudp_config_t { 5 | struct sockaddr_in bindaddr; 6 | struct sockaddr_in relayaddr; 7 | // TODO: outgoingaddr; 8 | struct sockaddr_in destaddr; 9 | char *login; 10 | char *password; 11 | uint16_t max_pktqueue; 12 | uint16_t udp_timeout; 13 | uint16_t udp_timeout_stream; 14 | } redudp_config; 15 | 16 | typedef struct redudp_instance_t { 17 | list_head list; 18 | redudp_config config; 19 | struct event listener; 20 | list_head clients; 21 | } redudp_instance; 22 | 23 | typedef struct redudp_client_t { 24 | list_head list; 25 | redudp_instance *instance; 26 | struct sockaddr_in clientaddr; 27 | struct sockaddr_in destaddr; 28 | int sender_fd; // shared between several clients socket (bound to `destaddr`) 29 | struct event timeout; 30 | struct bufferevent *relay; 31 | struct event udprelay; 32 | struct sockaddr_in udprelayaddr; 33 | int state; // it's used by bottom layer 34 | time_t first_event; 35 | time_t last_client_event; 36 | time_t last_relay_event; 37 | unsigned int queue_len; 38 | list_head queue; 39 | } redudp_client; 40 | 41 | typedef struct enqueued_packet_t { 42 | list_head list; 43 | size_t len; 44 | char data[1]; 45 | } enqueued_packet; 46 | 47 | /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ 48 | /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ 49 | #endif /* REDUDP_H */ 50 | -------------------------------------------------------------------------------- /socks4.c: -------------------------------------------------------------------------------- 1 | /* redsocks - transparent TCP-to-proxy redirector 2 | * Copyright (C) 2007-2018 Leonid Evdokimov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | #include 18 | #include 19 | #include 20 | #include "log.h" 21 | #include "redsocks.h" 22 | 23 | typedef enum socks4_state_t { 24 | socks4_new, 25 | socks4_request_sent, 26 | socks4_reply_came, 27 | socks4_MAX, 28 | } socks4_state; 29 | 30 | typedef struct socks4_req_t { 31 | uint8_t ver; 32 | uint8_t cmd; 33 | uint16_t port; 34 | uint32_t addr; 35 | char login[1]; // we need at least zero-byte 36 | } PACKED socks4_req; 37 | 38 | const int socks4_ver = 4; 39 | const int socks4_cmd_connect = 1; 40 | const int socks4_cmd_bind = 2; 41 | 42 | typedef struct socks4_reply_t { 43 | uint8_t ver; 44 | uint8_t status; 45 | uint16_t port; 46 | uint32_t addr; 47 | } PACKED socks4_reply; 48 | 49 | const int socks4_status_ok = 90; 50 | const int socks4_status_fail = 91; 51 | const int socks4_status_no_ident = 92; 52 | const int socks4_status_fake_ident = 93; 53 | 54 | 55 | static void socks4_instance_init(redsocks_instance *instance) 56 | { 57 | if (instance->config.password) 58 | log_error(LOG_WARNING, "password <%s> is ignored for socks4 connections", instance->config.password); 59 | } 60 | 61 | static void socks4_client_init(redsocks_client *client) 62 | { 63 | client->state = socks4_new; 64 | } 65 | 66 | 67 | static void socks4_read_cb(struct bufferevent *buffev, void *_arg) 68 | { 69 | redsocks_client *client = _arg; 70 | 71 | assert(client->state >= socks4_request_sent); 72 | 73 | redsocks_touch_client(client); 74 | 75 | if (client->state == socks4_request_sent) { 76 | socks4_reply reply; 77 | 78 | if (redsocks_read_expected(client, buffev->input, &reply, sizes_greater_equal, sizeof(reply)) < 0) 79 | return; 80 | 81 | client->state = socks4_reply_came; 82 | if (reply.ver != 0) { 83 | redsocks_log_error(client, LOG_NOTICE, "Socks4 server reported unexpected reply version..."); 84 | redsocks_drop_client(client); 85 | } 86 | else if (reply.status == socks4_status_ok) 87 | redsocks_start_relay(client); 88 | else { 89 | redsocks_log_error(client, LOG_NOTICE, "Socks4 server status: %s (%i)", 90 | reply.status == socks4_status_fail ? "fail" : 91 | reply.status == socks4_status_no_ident ? "no ident" : 92 | reply.status == socks4_status_fake_ident ? "fake ident" : "?", 93 | reply.status); 94 | redsocks_drop_client(client); 95 | } 96 | } 97 | } 98 | 99 | static struct evbuffer *socks4_mkconnect(redsocks_client *client) 100 | { 101 | const redsocks_config *config = &client->instance->config; 102 | const char *username = config->login ? config->login : ""; 103 | // space for \0 comes from socks4_req->login 104 | size_t username_len = strlen(username); 105 | size_t len = sizeof(socks4_req) + username_len; 106 | socks4_req *req = calloc(1, len); 107 | 108 | req->ver = socks4_ver; 109 | req->cmd = socks4_cmd_connect; 110 | req->port = client->destaddr.sin_port; 111 | req->addr = client->destaddr.sin_addr.s_addr; 112 | memcpy(req->login, username, username_len + 1); 113 | 114 | struct evbuffer *ret = mkevbuffer(req, len); 115 | free(req); 116 | return ret; 117 | } 118 | 119 | static void socks4_write_cb(struct bufferevent *buffev, void *_arg) 120 | { 121 | redsocks_client *client = _arg; 122 | 123 | redsocks_touch_client(client); 124 | 125 | if (client->state == socks4_new) { 126 | redsocks_write_helper( 127 | buffev, client, 128 | socks4_mkconnect, socks4_request_sent, sizeof(socks4_reply) 129 | ); 130 | } 131 | else if (client->state >= socks4_request_sent) { 132 | bufferevent_disable(buffev, EV_WRITE); 133 | } 134 | } 135 | 136 | 137 | relay_subsys socks4_subsys = 138 | { 139 | .name = "socks4", 140 | .payload_len = 0, 141 | .instance_payload_len = 0, 142 | .readcb = socks4_read_cb, 143 | .writecb = socks4_write_cb, 144 | .init = socks4_client_init, 145 | .instance_init = socks4_instance_init, 146 | }; 147 | 148 | 149 | /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ 150 | /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ 151 | -------------------------------------------------------------------------------- /socks5.c: -------------------------------------------------------------------------------- 1 | /* redsocks - transparent TCP-to-proxy redirector 2 | * Copyright (C) 2007-2018 Leonid Evdokimov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include "utils.h" 22 | #include "log.h" 23 | #include "redsocks.h" 24 | #include "socks5.h" 25 | 26 | typedef enum socks5_state_t { 27 | socks5_new, 28 | socks5_method_sent, 29 | socks5_auth_sent, 30 | socks5_request_sent, 31 | socks5_skip_domain, 32 | socks5_skip_address, 33 | socks5_MAX, 34 | } socks5_state; 35 | 36 | typedef struct socks5_client_t { 37 | int do_password; // 1 - password authentication is possible 38 | int to_skip; // valid while reading last reply (after main request) 39 | } socks5_client; 40 | 41 | const char *socks5_strstatus[] = { 42 | "ok", 43 | "server failure", 44 | "connection not allowed by ruleset", 45 | "network unreachable", 46 | "host unreachable", 47 | "connection refused", 48 | "TTL expired", 49 | "command not supported", 50 | "address type not supported", 51 | }; 52 | const size_t socks5_strstatus_len = SIZEOF_ARRAY(socks5_strstatus); 53 | 54 | const char* socks5_status_to_str(int socks5_status) 55 | { 56 | if (0 <= socks5_status && socks5_status < socks5_strstatus_len) { 57 | return socks5_strstatus[socks5_status]; 58 | } 59 | else { 60 | return ""; 61 | } 62 | } 63 | 64 | bool socks5_is_valid_cred(const char *login, const char *password) 65 | { 66 | if (!login || !password) 67 | return false; 68 | if (strlen(login) > 255) { 69 | log_error(LOG_WARNING, "Socks5 login can't be more than 255 chars, <%s> is too long", login); 70 | return false; 71 | } 72 | if (strlen(password) > 255) { 73 | log_error(LOG_WARNING, "Socks5 password can't be more than 255 chars, <%s> is too long", password); 74 | return false; 75 | } 76 | return true; 77 | } 78 | 79 | static void socks5_instance_init(redsocks_instance *instance) 80 | { 81 | redsocks_config *config = &instance->config; 82 | if (config->login || config->password) { 83 | bool deauth = false; 84 | if (config->login && config->password) { 85 | deauth = ! socks5_is_valid_cred(config->login, config->password); 86 | } else { 87 | log_error(LOG_WARNING, "Socks5 needs either both login and password or none of them"); 88 | deauth = true; 89 | } 90 | if (deauth) { 91 | free(config->login); 92 | free(config->password); 93 | config->login = config->password = NULL; 94 | } 95 | } 96 | } 97 | 98 | static void socks5_client_init(redsocks_client *client) 99 | { 100 | socks5_client *socks5 = red_payload(client); 101 | const redsocks_config *config = &client->instance->config; 102 | 103 | client->state = socks5_new; 104 | socks5->do_password = (config->login && config->password) ? 1 : 0; 105 | } 106 | 107 | static struct evbuffer *socks5_mkmethods(redsocks_client *client) 108 | { 109 | socks5_client *socks5 = red_payload(client); 110 | return socks5_mkmethods_plain(socks5->do_password); 111 | } 112 | 113 | struct evbuffer *socks5_mkmethods_plain(int do_password) 114 | { 115 | assert(do_password == 0 || do_password == 1); 116 | int len = sizeof(socks5_method_req) + do_password; 117 | socks5_method_req *req = calloc(1, len); 118 | 119 | req->ver = socks5_ver; 120 | req->num_methods = 1 + do_password; 121 | req->methods[0] = socks5_auth_none; 122 | if (do_password) 123 | req->methods[1] = socks5_auth_password; 124 | 125 | struct evbuffer *ret = mkevbuffer(req, len); 126 | free(req); 127 | return ret; 128 | } 129 | 130 | static struct evbuffer *socks5_mkpassword(redsocks_client *client) 131 | { 132 | return socks5_mkpassword_plain(client->instance->config.login, client->instance->config.password); 133 | } 134 | 135 | struct evbuffer *socks5_mkpassword_plain(const char *login, const char *password) 136 | { 137 | size_t ulen = strlen(login); 138 | size_t plen = strlen(password); 139 | size_t length = 1 /* version */ + 1 + ulen + 1 + plen; 140 | uint8_t req[length]; 141 | 142 | req[0] = socks5_password_ver; // RFC 1929 says so 143 | req[1] = ulen; 144 | memcpy(&req[2], login, ulen); 145 | req[2+ulen] = plen; 146 | memcpy(&req[3+ulen], password, plen); 147 | return mkevbuffer(req, length); 148 | } 149 | 150 | struct evbuffer *socks5_mkcommand_plain(int socks5_cmd, const struct sockaddr_in *destaddr) 151 | { 152 | struct { 153 | socks5_req head; 154 | socks5_addr_ipv4 ip; 155 | } PACKED req; 156 | 157 | assert(destaddr->sin_family == AF_INET); 158 | 159 | req.head.ver = socks5_ver; 160 | req.head.cmd = socks5_cmd; 161 | req.head.reserved = 0; 162 | req.head.addrtype = socks5_addrtype_ipv4; 163 | req.ip.addr = destaddr->sin_addr.s_addr; 164 | req.ip.port = destaddr->sin_port; 165 | return mkevbuffer(&req, sizeof(req)); 166 | } 167 | 168 | static struct evbuffer *socks5_mkconnect(redsocks_client *client) 169 | { 170 | return socks5_mkcommand_plain(socks5_cmd_connect, &client->destaddr); 171 | } 172 | 173 | static void socks5_write_cb(struct bufferevent *buffev, void *_arg) 174 | { 175 | redsocks_client *client = _arg; 176 | 177 | redsocks_touch_client(client); 178 | 179 | if (client->state == socks5_new) { 180 | redsocks_write_helper( 181 | buffev, client, 182 | socks5_mkmethods, socks5_method_sent, sizeof(socks5_method_reply) 183 | ); 184 | } 185 | } 186 | 187 | const char* socks5_is_known_auth_method(socks5_method_reply *reply, int do_password) 188 | { 189 | if (reply->ver != socks5_ver) 190 | return "Socks5 server reported unexpected auth methods reply version..."; 191 | else if (reply->method == socks5_auth_invalid) 192 | return "Socks5 server refused all our auth methods."; 193 | else if (reply->method != socks5_auth_none && !(reply->method == socks5_auth_password && do_password)) 194 | return "Socks5 server requested unexpected auth method..."; 195 | else 196 | return NULL; 197 | } 198 | 199 | static void socks5_read_auth_methods(struct bufferevent *buffev, redsocks_client *client, socks5_client *socks5) 200 | { 201 | socks5_method_reply reply; 202 | const char *error = NULL; 203 | 204 | if (redsocks_read_expected(client, buffev->input, &reply, sizes_equal, sizeof(reply)) < 0) 205 | return; 206 | 207 | error = socks5_is_known_auth_method(&reply, socks5->do_password); 208 | if (error) { 209 | redsocks_log_error(client, LOG_NOTICE, "socks5_is_known_auth_method: %s", error); 210 | redsocks_drop_client(client); 211 | } 212 | else if (reply.method == socks5_auth_none) { 213 | redsocks_write_helper( 214 | buffev, client, 215 | socks5_mkconnect, socks5_request_sent, sizeof(socks5_reply) 216 | ); 217 | } 218 | else if (reply.method == socks5_auth_password) { 219 | redsocks_write_helper( 220 | buffev, client, 221 | socks5_mkpassword, socks5_auth_sent, sizeof(socks5_auth_reply) 222 | ); 223 | } 224 | } 225 | 226 | static void socks5_read_auth_reply(struct bufferevent *buffev, redsocks_client *client, socks5_client *socks5) 227 | { 228 | socks5_auth_reply reply; 229 | 230 | if (redsocks_read_expected(client, buffev->input, &reply, sizes_equal, sizeof(reply)) < 0) 231 | return; 232 | 233 | if (reply.ver != socks5_password_ver) { 234 | redsocks_log_error(client, LOG_NOTICE, "Socks5 server reported unexpected auth reply version %d", reply.ver); 235 | redsocks_drop_client(client); 236 | } 237 | else if (reply.status == socks5_password_passed) 238 | redsocks_write_helper( 239 | buffev, client, 240 | socks5_mkconnect, socks5_request_sent, sizeof(socks5_reply) 241 | ); 242 | else { 243 | redsocks_log_error(client, LOG_NOTICE, "Socks5 auth failure, status %i", reply.status); 244 | redsocks_drop_client(client); 245 | } 246 | } 247 | 248 | static void socks5_read_reply(struct bufferevent *buffev, redsocks_client *client, socks5_client *socks5) 249 | { 250 | socks5_reply reply; 251 | 252 | if (redsocks_read_expected(client, buffev->input, &reply, sizes_greater_equal, sizeof(reply)) < 0) 253 | return; 254 | 255 | if (reply.ver != socks5_ver) { 256 | redsocks_log_error(client, LOG_NOTICE, "Socks5 server reported unexpected reply version..."); 257 | redsocks_drop_client(client); 258 | } 259 | else if (reply.status == socks5_status_succeeded) { 260 | socks5_state nextstate; 261 | size_t len; 262 | 263 | if (reply.addrtype == socks5_addrtype_ipv4) { 264 | len = socks5->to_skip = sizeof(socks5_addr_ipv4); 265 | nextstate = socks5_skip_address; 266 | } 267 | else if (reply.addrtype == socks5_addrtype_ipv6) { 268 | len = socks5->to_skip = sizeof(socks5_addr_ipv6); 269 | nextstate = socks5_skip_address; 270 | } 271 | else if (reply.addrtype == socks5_addrtype_domain) { 272 | socks5_addr_domain domain; 273 | len = sizeof(domain.size); 274 | nextstate = socks5_skip_domain; 275 | } 276 | else { 277 | redsocks_log_error(client, LOG_NOTICE, "Socks5 server reported unexpected address type..."); 278 | redsocks_drop_client(client); 279 | return; 280 | } 281 | 282 | redsocks_write_helper( 283 | buffev, client, 284 | NULL, nextstate, len 285 | ); 286 | } 287 | else { 288 | redsocks_log_error(client, LOG_NOTICE, "Socks5 server status: %s (%i)", 289 | /* 0 <= reply.status && */ reply.status < SIZEOF_ARRAY(socks5_strstatus) 290 | ? socks5_strstatus[reply.status] : "?", reply.status); 291 | redsocks_drop_client(client); 292 | } 293 | } 294 | 295 | static void socks5_read_cb(struct bufferevent *buffev, void *_arg) 296 | { 297 | redsocks_client *client = _arg; 298 | socks5_client *socks5 = red_payload(client); 299 | 300 | redsocks_touch_client(client); 301 | 302 | if (client->state == socks5_method_sent) { 303 | socks5_read_auth_methods(buffev, client, socks5); 304 | } 305 | else if (client->state == socks5_auth_sent) { 306 | socks5_read_auth_reply(buffev, client, socks5); 307 | } 308 | else if (client->state == socks5_request_sent) { 309 | socks5_read_reply(buffev, client, socks5); 310 | } 311 | else if (client->state == socks5_skip_domain) { 312 | socks5_addr_ipv4 ipv4; // all socks5_addr*.port are equal 313 | uint8_t size; 314 | if (redsocks_read_expected(client, buffev->input, &size, sizes_greater_equal, sizeof(size)) < 0) 315 | return; 316 | socks5->to_skip = size + sizeof(ipv4.port); 317 | redsocks_write_helper( 318 | buffev, client, 319 | NULL, socks5_skip_address, socks5->to_skip 320 | ); 321 | } 322 | else if (client->state == socks5_skip_address) { 323 | uint8_t data[socks5->to_skip]; 324 | if (redsocks_read_expected(client, buffev->input, data, sizes_greater_equal, socks5->to_skip) < 0) 325 | return; 326 | redsocks_start_relay(client); 327 | } 328 | else { 329 | redsocks_drop_client(client); 330 | } 331 | } 332 | 333 | relay_subsys socks5_subsys = 334 | { 335 | .name = "socks5", 336 | .payload_len = sizeof(socks5_client), 337 | .instance_payload_len = 0, 338 | .readcb = socks5_read_cb, 339 | .writecb = socks5_write_cb, 340 | .init = socks5_client_init, 341 | .instance_init = socks5_instance_init, 342 | }; 343 | 344 | 345 | /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ 346 | /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ 347 | -------------------------------------------------------------------------------- /socks5.h: -------------------------------------------------------------------------------- 1 | #ifndef SOCKS5_H 2 | #define SOCKS5_H 3 | 4 | #include 5 | #include "utils.h" 6 | 7 | typedef struct socks5_method_req_t { 8 | uint8_t ver; 9 | uint8_t num_methods; 10 | uint8_t methods[1]; // at least one 11 | } PACKED socks5_method_req; 12 | 13 | typedef struct socks5_method_reply_t { 14 | uint8_t ver; 15 | uint8_t method; 16 | } PACKED socks5_method_reply; 17 | 18 | static const int socks5_ver = 5; 19 | 20 | static const int socks5_auth_none = 0x00; 21 | static const int socks5_auth_gssapi = 0x01; 22 | static const int socks5_auth_password = 0x02; 23 | static const int socks5_auth_invalid = 0xFF; 24 | 25 | typedef struct socks5_auth_reply_t { 26 | uint8_t ver; 27 | uint8_t status; 28 | } PACKED socks5_auth_reply; 29 | 30 | static const int socks5_password_ver = 0x01; 31 | static const int socks5_password_passed = 0x00; 32 | 33 | 34 | typedef struct socks5_addr_ipv4_t { 35 | uint32_t addr; 36 | uint16_t port; 37 | } PACKED socks5_addr_ipv4; 38 | 39 | typedef struct socks5_addr_domain_t { 40 | uint8_t size; 41 | uint8_t more[1]; 42 | /* uint16_t port; */ 43 | } PACKED socks5_addr_domain; 44 | 45 | typedef struct socks5_addr_ipv6_t { 46 | uint8_t addr[16]; 47 | uint16_t port; 48 | } PACKED socks5_addr_ipv6; 49 | 50 | typedef struct socks5_req_t { 51 | uint8_t ver; 52 | uint8_t cmd; 53 | uint8_t reserved; 54 | uint8_t addrtype; 55 | /* socks5_addr_* */ 56 | } PACKED socks5_req; 57 | 58 | typedef struct socks5_reply_t { 59 | uint8_t ver; 60 | uint8_t status; 61 | uint8_t reserved; 62 | uint8_t addrtype; 63 | /* socks5_addr_* */ 64 | } PACKED socks5_reply; 65 | 66 | typedef struct socks5_udp_preabmle_t { 67 | uint16_t reserved; 68 | uint8_t frag_no; 69 | uint8_t addrtype; /* 0x01 for IPv4 */ 70 | /* socks5_addr_* */ 71 | socks5_addr_ipv4 ip; /* I support only IPv4 at the moment */ 72 | } PACKED socks5_udp_preabmle; 73 | 74 | static const int socks5_reply_maxlen = 512; // as domain name can't be longer than 256 bytes 75 | static const int socks5_addrtype_ipv4 = 1; 76 | static const int socks5_addrtype_domain = 3; 77 | static const int socks5_addrtype_ipv6 = 4; 78 | static const int socks5_status_succeeded = 0; 79 | static const int socks5_status_server_failure = 1; 80 | static const int socks5_status_connection_not_allowed_by_ruleset = 2; 81 | static const int socks5_status_Network_unreachable = 3; 82 | static const int socks5_status_Host_unreachable = 4; 83 | static const int socks5_status_Connection_refused = 5; 84 | static const int socks5_status_TTL_expired = 6; 85 | static const int socks5_status_Command_not_supported = 7; 86 | static const int socks5_status_Address_type_not_supported = 8; 87 | 88 | 89 | const char* socks5_status_to_str(int socks5_status); 90 | bool socks5_is_valid_cred(const char *login, const char *password); 91 | 92 | struct evbuffer *socks5_mkmethods_plain(int do_password); 93 | struct evbuffer *socks5_mkpassword_plain(const char *login, const char *password); 94 | const char* socks5_is_known_auth_method(socks5_method_reply *reply, int do_password); 95 | 96 | static const int socks5_cmd_connect = 1; 97 | static const int socks5_cmd_bind = 2; 98 | static const int socks5_cmd_udp_associate = 3; 99 | struct evbuffer *socks5_mkcommand_plain(int socks5_cmd, const struct sockaddr_in *destaddr); 100 | 101 | 102 | /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ 103 | /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ 104 | #endif /* SOCKS5_H */ 105 | -------------------------------------------------------------------------------- /tdestroy.c: -------------------------------------------------------------------------------- 1 | #ifdef __APPLE__ 2 | 3 | #define _GNU_SOURCE 4 | #include 5 | #include 6 | #include "tsearch.h" 7 | 8 | void tdestroy(void *root, void (*freekey)(void *)) 9 | { 10 | struct node *r = root; 11 | 12 | if (r == 0) 13 | return; 14 | tdestroy(r->a[0], freekey); 15 | tdestroy(r->a[1], freekey); 16 | if (freekey) freekey((void *)r->key); 17 | free(r); 18 | } 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /tests/build: -------------------------------------------------------------------------------- 1 | #!/bin/bash -ex 2 | 3 | (cd .. && make) 4 | cp -a ../redsocks ./regw/redsocks 5 | 6 | for img in *; do 7 | if [ -d "$img" -a -f "$img/Dockerfile" ]; then 8 | sudo docker build -t redsocks/"$img" ./"$img"/ 9 | fi 10 | done 11 | -------------------------------------------------------------------------------- /tests/cleanup: -------------------------------------------------------------------------------- 1 | #!/bin/bash -x 2 | docker ps 3 | vms=$(echo gw web inetd regw dante-{0..1} squid-{8..9} tank{10..26}) 4 | docker stop --time 1 $vms 5 | docker rm $vms 6 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | from functools import partial 2 | from multiprocessing.dummy import Pool as ThrPool 3 | from subprocess import check_call, check_output, STDOUT 4 | import multiprocessing 5 | import os 6 | import time 7 | 8 | import pytest 9 | 10 | # 10.0.1.0/24 (br-web) -- web, squid, inetd, gw 11 | # 10.0.2.0/24 (br-tank) -- tank, openwrt 12 | # 10.0.8.0/24 (br-txrx) -- gw, openwrt 13 | 14 | BR = { 15 | '10.0.1': 'web', 16 | '10.0.8': 'txrx', 17 | '10.0.2': 'tank', 18 | } 19 | 20 | GW = { 21 | 'web': '10.0.1.1', 22 | 'txrx': '10.0.8.1', 23 | 'tank': '10.0.2.123', 24 | } 25 | 26 | SLEEPING_BEAST = 'sleep 3600' 27 | DOCKER_CMD = os.environ.get('DOCKER_CMD', '/usr/bin/docker') 28 | PIPEWORK_CMD = os.environ.get('PIPEWORK_CMD', '/usr/bin/pipework') 29 | 30 | class VM(object): 31 | def __init__(self, name, tag, ip4=None, cmd='', docker_opt=''): 32 | self.docker = DOCKER_CMD 33 | self.pipework = PIPEWORK_CMD 34 | self.netns = '/var/run/netns' 35 | self.dns = '8.8.8.8' 36 | 37 | self.name, self.tag = name, tag 38 | self.cmd, self.docker_opt = cmd, docker_opt 39 | if ip4: 40 | self.ip4 = ip4 41 | self.sha = self.output('sudo docker run --detach --dns {dns} --name {name} --hostname {name} {docker_opt} {tag} {cmd}') 42 | self.pid = int(self.output('docker inspect -f {{{{.State.Pid}}}} {sha}')) 43 | if not os.path.exists(self.netns): 44 | self.call('sudo mkdir {netns}') 45 | self.call('sudo ln -sf /proc/{pid}/ns/net {netns}/{name}') 46 | self.net() 47 | while cmd != SLEEPING_BEAST and 'LISTEN' not in self.do('netstat -ltn'): 48 | time.sleep(0.1) 49 | 50 | def net(self): 51 | self.net_noext() 52 | self.net_br() 53 | 54 | def net_br(self): 55 | self.net_br_gw() 56 | 57 | def net_noext(self): 58 | self.netcall('ip link set dev eth0 down') 59 | self.netcall('ip route replace unreachable {dns}/32') 60 | 61 | def net_br_gw(self): 62 | self.call('sudo {pipework} br-{br} -i {intif} -l {vethif} {name} {ip4}/24@{gw4}') 63 | 64 | def net_br_nogw(self): 65 | self.call('sudo {pipework} br-{br} -i {intif} -l {vethif} {name} {ip4}/24') 66 | 67 | @property 68 | def br(self): 69 | return BR[self.ip4.rsplit('.', 1)[0]] 70 | @property 71 | def gw4(self): 72 | return GW[self.br] 73 | @property 74 | def intif(self): 75 | return {'web': 'ethw', 'tank': 'etht', 'txrx': 'ethx'}[self.br] 76 | @property 77 | def vethif(self): 78 | return ('v' + self.intif + self.name)[:15] # IFNAMSIZ 16 79 | 80 | def assert_good_death(self): 81 | pass 82 | 83 | def close(self): 84 | if hasattr(self, 'sha'): 85 | self.call('sudo docker stop --time 1 {sha}') 86 | self.assert_good_death() 87 | if not getattr(self, 'preserve_root', False): 88 | self.call('sudo docker rm {sha}') 89 | del self.sha 90 | def fmt(self, cmd): 91 | ctx = self.__dict__.copy() 92 | for i in xrange(len(dir(self))): 93 | try: 94 | ret = cmd.format(**ctx).split() 95 | break 96 | except KeyError, e: 97 | key = e.args[0] 98 | ctx[key] = getattr(self, key) 99 | return ret 100 | def output(self, cmd, **kwargs): 101 | return check_output(self.fmt(cmd), **kwargs) 102 | def logs(self, since=None): 103 | out = self.output('sudo docker logs {sha}', stderr=STDOUT) 104 | out = [_.split(None, 1) for _ in out.split('\n')] 105 | out = [(float(_[0]), _[1]) for _ in out if len(_) == 2 and _[0][:10].isdigit()] 106 | if since is not None: 107 | out = [_ for _ in out if _[0] >= since] 108 | return out 109 | def call(self, cmd): 110 | check_call(self.fmt(cmd)) 111 | def do(self, cmd): 112 | return self.output('sudo docker exec {sha} ' + cmd) 113 | def netcall(self, cmd): 114 | return self.output('sudo ip netns exec {name} ' + cmd) 115 | def lsfd(self): 116 | out = self.output('sudo lsof -p {pid} -F f') 117 | out = [int(_[1:]) for _ in out.split() if _[0] == 'f' and _[1:].isdigit()] 118 | assert len(out) > 0 119 | return out 120 | 121 | class WebVM(VM): 122 | def __init__(self): 123 | VM.__init__(self, 'web', 'redsocks/web', '10.0.1.80') 124 | 125 | class InetdVM(VM): 126 | def __init__(self): 127 | VM.__init__(self, 'inetd', 'redsocks/inetd', '10.0.1.13') 128 | 129 | class SquidVM(VM): 130 | def __init__(self, no): 131 | VM.__init__(self, 'squid-%d' % no, 'redsocks/squid', '10.0.1.%d' % no, 132 | docker_opt='--ulimit nofile=65535:65535', 133 | cmd='/etc/squid3/squid-%d.conf' % no) 134 | def net(self): 135 | self.net_br_nogw() 136 | self.netcall('ip route replace 10.0.0.0/16 via 10.0.1.1') 137 | 138 | class DanteVM(VM): 139 | def __init__(self, no): 140 | VM.__init__(self, 'dante-%d' % no, 'redsocks/dante', '10.0.1.%d' % (180 + no), 141 | cmd='/etc/danted-%d.conf' % (1080 + no)) 142 | def net(self): 143 | self.net_br_nogw() 144 | self.netcall('ip route replace 10.0.0.0/16 via 10.0.1.1') 145 | 146 | 147 | class GwVM(VM): 148 | def __init__(self): 149 | VM.__init__(self, 'gw', 'ubuntu:14.04', cmd=SLEEPING_BEAST) 150 | def net_br(self): 151 | self.ip4 = '10.0.1.1' 152 | self.net_br_nogw() 153 | self.ip4 = '10.0.8.1' 154 | self.net_br_nogw() 155 | del self.ip4 156 | self.netcall('ip route replace unreachable 10.0.2.0/24') 157 | self.netcall('iptables -A FORWARD --destination 10.0.1.66/32 -j DROP') 158 | 159 | class TankVM(VM): 160 | def __init__(self, no): 161 | assert 1 <= no <= 100 162 | VM.__init__(self, 'tank%d' % no, 'redsocks/tank', '10.0.2.%d' % no, cmd=SLEEPING_BEAST) 163 | 164 | class RegwVM(VM): 165 | def __init__(self): 166 | kw = {} 167 | self.debug = os.environ.get('DEBUG_TEST', '') 168 | if self.debug: 169 | self.preserve_root = True 170 | if self.debug != 'logs': 171 | kw['cmd'] = { 172 | 'valgrind': 'valgrind --leak-check=full --show-leak-kinds=all /usr/local/sbin/redsocks -c /usr/local/etc/redsocks.conf', 173 | 'strace': 'strace -ttt /usr/local/sbin/redsocks -c /usr/local/etc/redsocks.conf', 174 | }[self.debug] 175 | VM.__init__(self, 'regw', 'redsocks/regw', **kw) 176 | def net_br(self): 177 | self.ip4 = '10.0.2.123' 178 | self.net_br_nogw() 179 | self.ip4 = '10.0.8.123' 180 | self.net_br_gw() 181 | del self.ip4 182 | for t in TANKS.values(): 183 | self.netcall('iptables -t nat -A PREROUTING --source 10.0.2.%d/32 --dest 10.0.1.0/24 -p tcp -j REDIRECT --to-port %d' % (t, 12340 + t - TANKS_BASE)) 184 | def assert_good_death(self): 185 | out = self.output('sudo docker logs {sha}', stderr=STDOUT) 186 | assert 'redsocks goes down' in out and 'There are connected clients during shutdown' not in out 187 | assert self.debug != 'valgrind' or 'no leaks are possible' in out 188 | 189 | CPU = object() 190 | MAX = object() 191 | def pmap(l, j=MAX): 192 | #return map(lambda x: x(), l) 193 | if j is MAX: 194 | j = len(l) 195 | elif j is CPU: 196 | j = multiprocessing.cpu_count() 197 | p = ThrPool(j) 198 | try: 199 | return p.map(lambda x: x(), l, chunksize=1) 200 | finally: 201 | p.close() 202 | p.join() 203 | 204 | TANKS_BASE = 10 205 | TANKS = { 206 | 'connect_none': TANKS_BASE + 0, 207 | 'connect_basic': TANKS_BASE + 1, 208 | 'connect_digest': TANKS_BASE + 2, 209 | 'socks5_none': TANKS_BASE + 3, 210 | 'socks5_auth': TANKS_BASE + 4, 211 | 'connect_nopass': TANKS_BASE + 5, 212 | 'connect_baduser': TANKS_BASE + 6, 213 | 'connect_badpass': TANKS_BASE + 7, 214 | 'socks5_nopass': TANKS_BASE + 8, 215 | 'socks5_baduser': TANKS_BASE + 9, 216 | 'socks5_badpass': TANKS_BASE + 10, 217 | 'httperr_connect_nopass': TANKS_BASE + 11, 218 | 'httperr_connect_baduser': TANKS_BASE + 12, 219 | 'httperr_connect_badpass': TANKS_BASE + 13, 220 | 'httperr_connect_digest': TANKS_BASE + 14, 221 | 'httperr_proxy_refuse': TANKS_BASE + 15, 222 | 'httperr_proxy_timeout': TANKS_BASE + 16, 223 | } 224 | 225 | class _Network(object): 226 | def __init__(self): 227 | assert os.path.exists(PIPEWORK_CMD) 228 | running = check_output('sudo docker ps -q'.split()).split() 229 | assert running == [] 230 | names = check_output('sudo docker ps -a --format {{.Names}}'.split()).split() 231 | assert 'regw' not in names 232 | vm = [ 233 | GwVM, 234 | WebVM, 235 | InetdVM, 236 | RegwVM, 237 | partial(SquidVM, 8), 238 | partial(SquidVM, 9), 239 | partial(DanteVM, 0), 240 | partial(DanteVM, 1), 241 | ] 242 | for t in TANKS.values(): 243 | vm.append(partial(TankVM, t)) 244 | self.vm = {_.name: _ for _ in pmap(vm, j=CPU)} # pmap saves ~5 seconds 245 | def close(self): 246 | check_output('sudo docker ps'.split()) 247 | pmap([_.close for _ in self.vm.values()]) # pmap saves ~7 seconds 248 | 249 | @pytest.fixture(scope="session") 250 | def net(request): 251 | n = _Network() 252 | request.addfinalizer(n.close) 253 | return n 254 | 255 | def pytest_addoption(parser): 256 | parser.addoption('--vmdebug', action='store_true', help='run `test_debug` test') 257 | 258 | def pytest_cmdline_preparse(args): 259 | if '--vmdebug' in args: 260 | args[:] = ['-k', 'test_vmdebug'] + args 261 | -------------------------------------------------------------------------------- /tests/dante/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:14.04 2 | 3 | RUN set -o xtrace \ 4 | && sed -i 's,^deb-src,# no src # &,; s,http://archive.ubuntu.com/ubuntu/,mirror://mirrors.ubuntu.com/mirrors.txt,' /etc/apt/sources.list \ 5 | && apt-get update \ 6 | && apt-get install -y dante-server 7 | 8 | RUN useradd sockusr --user-group \ 9 | --home-dir /nonexistent --no-create-home \ 10 | --shell /usr/sbin/nologin \ 11 | --password '$6$U1HPxoVq$XFqhRetreV3068UCwQA//fGVFUfwfyqeiYpCpeUFAhuMi/wjOhJSzUxb4wUqt9vRnWjO0CDPMkE40ptHWrrIz.' 12 | 13 | COPY danted-*.conf /etc/ 14 | COPY danted-waitif /usr/sbin/ 15 | 16 | ENTRYPOINT ["/usr/sbin/danted-waitif", "-d", "-f"] 17 | CMD ["/etc/danted-1080.conf"] 18 | -------------------------------------------------------------------------------- /tests/dante/danted-1080.conf: -------------------------------------------------------------------------------- 1 | logoutput: stderr 2 | internal: 0.0.0.0 port = 1080 3 | external: ethw 4 | method: none 5 | clientmethod: none 6 | 7 | #extension: bind 8 | 9 | # TCP addr rules 10 | 11 | client pass { 12 | from: 10.0.8.123/32 port 1-65535 to: 0.0.0.0/0 13 | log: connect error 14 | } 15 | client block { 16 | from: 0.0.0.0/0 to: 0.0.0.0/0 17 | log: connect error 18 | } 19 | 20 | # Socks protocol rules 21 | 22 | block { 23 | from: 0.0.0.0/0 to: 127.0.0.0/8 24 | log: connect error 25 | } 26 | pass { 27 | from: 10.0.8.123/32 to: 0.0.0.0/0 28 | log: connect error 29 | } 30 | block { 31 | from: 0.0.0.0/0 to: 0.0.0.0/0 32 | log: connect error 33 | } 34 | -------------------------------------------------------------------------------- /tests/dante/danted-1081.conf: -------------------------------------------------------------------------------- 1 | logoutput: stderr 2 | internal: 0.0.0.0 port = 1081 3 | external: ethw 4 | method: username 5 | clientmethod: none 6 | 7 | #extension: bind 8 | 9 | # TCP addr rules 10 | 11 | client pass { 12 | from: 10.0.8.123/32 port 1-65535 to: 0.0.0.0/0 13 | log: connect error 14 | } 15 | client block { 16 | from: 0.0.0.0/0 to: 0.0.0.0/0 17 | log: connect error 18 | } 19 | 20 | # Socks protocol rules 21 | 22 | block { 23 | from: 0.0.0.0/0 to: 127.0.0.0/8 24 | log: connect error 25 | } 26 | pass { 27 | from: 10.0.8.123/32 to: 0.0.0.0/0 28 | log: connect error 29 | } 30 | block { 31 | from: 0.0.0.0/0 to: 0.0.0.0/0 32 | log: connect error 33 | } 34 | -------------------------------------------------------------------------------- /tests/dante/danted-waitif: -------------------------------------------------------------------------------- 1 | #!/bin/sh -x 2 | while ! ip link show ethw up 2>/dev/null | grep -q UP; do 3 | sleep 0.1 4 | done 5 | exec /usr/sbin/danted "$@" 6 | -------------------------------------------------------------------------------- /tests/inetd/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:14.04 2 | 3 | RUN set -o xtrace \ 4 | && sed -i 's,^deb-src,# no src # &,; s,http://archive.ubuntu.com/ubuntu/,mirror://mirrors.ubuntu.com/mirrors.txt,' /etc/apt/sources.list \ 5 | && apt-get update \ 6 | && apt-get install -y xinetd 7 | 8 | RUN set -o xtrace \ 9 | && apt-get install -y iperf 10 | 11 | COPY testing /etc/xinetd.d/testing 12 | 13 | CMD ["/usr/sbin/xinetd", "-dontfork", "-pidfile", "/run/xinetd.pid", "-inetd_ipv6"] 14 | -------------------------------------------------------------------------------- /tests/inetd/testing: -------------------------------------------------------------------------------- 1 | service daytime 2 | { 3 | disable = no 4 | type = INTERNAL 5 | id = daytime-stream 6 | socket_type = stream 7 | protocol = tcp 8 | user = root 9 | wait = no 10 | flags = IPv6 11 | } 12 | 13 | service discard 14 | { 15 | disable = no 16 | type = INTERNAL 17 | id = discard-stream 18 | socket_type = stream 19 | protocol = tcp 20 | user = root 21 | wait = no 22 | flags = IPv6 23 | } 24 | 25 | service echo 26 | { 27 | disable = no 28 | type = INTERNAL 29 | id = echo-stream 30 | socket_type = stream 31 | protocol = tcp 32 | user = root 33 | wait = no 34 | flags = IPv6 35 | } 36 | 37 | service chargen 38 | { 39 | disable = no 40 | type = INTERNAL 41 | id = chargen-stream 42 | socket_type = stream 43 | protocol = tcp 44 | user = root 45 | wait = no 46 | flags = IPv6 47 | } 48 | -------------------------------------------------------------------------------- /tests/login: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | vm=$1 3 | shift 4 | if [ $# = 0 ]; then 5 | set -- /bin/bash 6 | fi 7 | if [ -t 0 -a -t 1 -a -t 2 ]; then 8 | tty="--tty" 9 | else 10 | tty="" 11 | fi 12 | set -x 13 | sudo docker exec --interactive $tty "$vm" "$@" 14 | -------------------------------------------------------------------------------- /tests/prlimit-nofile.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | // prlimit(1) is util-linux 2.21+ and I'm stuck with 2.20 at the moment 7 | 8 | int main(int argc, char *argv[]) 9 | { 10 | if (argc != 3) 11 | errx(EXIT_FAILURE, "Usage: %s ", argv[0]); 12 | 13 | pid_t pid = atoi(argv[1]); 14 | rlim_t soft = atoi(argv[2]); 15 | 16 | struct rlimit rl; 17 | if (prlimit(pid, RLIMIT_NOFILE, NULL, &rl) == -1) 18 | err(EXIT_FAILURE, "prlimit(%d, RLIMIT_NOFILE, NULL, %p)", pid, &rl); 19 | 20 | if (rl.rlim_max < soft) 21 | errx(EXIT_FAILURE, "rlim_max = %ld, requested limit = %ld", rl.rlim_max, soft); 22 | 23 | rl.rlim_cur = soft; 24 | 25 | if (prlimit(pid, RLIMIT_NOFILE, &rl, NULL) == -1) 26 | err(EXIT_FAILURE, "prlimit(%d, RLIMIT_NOFILE, %p, NULL)", pid, &rl); 27 | 28 | return 0; 29 | } 30 | -------------------------------------------------------------------------------- /tests/regw/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:14.04 2 | 3 | RUN set -o xtrace \ 4 | && sed -i 's,^deb-src,# no src # &,; s,http://archive.ubuntu.com/ubuntu/,mirror://mirrors.ubuntu.com/mirrors.txt,' /etc/apt/sources.list \ 5 | && apt-get update \ 6 | && apt-get install -y libevent-2.0-5 valgrind curl strace 7 | 8 | COPY redsocks /usr/local/sbin/ 9 | COPY redsocks.conf /usr/local/etc/ 10 | CMD ["/usr/local/sbin/redsocks", "-c", "/usr/local/etc/redsocks.conf"] 11 | -------------------------------------------------------------------------------- /tests/regw/redsocks.conf: -------------------------------------------------------------------------------- 1 | base { 2 | log_debug = on; 3 | log_info = on; 4 | log = stderr; 5 | daemon = off; 6 | redirector = iptables; 7 | redsocks_conn_max = 8; 8 | } 9 | 10 | redsocks { 11 | local_ip = 0.0.0.0; 12 | local_port = 12340; 13 | ip = 10.0.1.8; 14 | port = 8080; 15 | type = http-connect; 16 | } 17 | 18 | redsocks { 19 | local_ip = 0.0.0.0; 20 | local_port = 12341; 21 | ip = 10.0.1.8; 22 | port = 8081; 23 | type = http-connect; 24 | login = basic_user; 25 | password = basic_password; 26 | } 27 | 28 | redsocks { 29 | local_ip = 0.0.0.0; 30 | local_port = 12342; 31 | ip = 10.0.1.9; 32 | port = 8081; 33 | type = http-connect; 34 | login = digest_user; 35 | password = digest_password; 36 | } 37 | 38 | redsocks { 39 | local_ip = 0.0.0.0; 40 | local_port = 12343; 41 | ip = 10.0.1.180; 42 | port = 1080; 43 | type = socks5; 44 | } 45 | 46 | redsocks { 47 | local_ip = 0.0.0.0; 48 | local_port = 12344; 49 | ip = 10.0.1.181; 50 | port = 1081; 51 | type = socks5; 52 | login = sockusr; 53 | password = sockpwd; 54 | } 55 | 56 | redsocks { 57 | local_ip = 0.0.0.0; 58 | local_port = 12345; 59 | ip = 10.0.1.9; 60 | port = 8081; 61 | type = http-connect; 62 | } 63 | 64 | redsocks { 65 | local_ip = 0.0.0.0; 66 | local_port = 12346; 67 | ip = 10.0.1.9; 68 | port = 8081; 69 | type = http-connect; 70 | login = luser; 71 | password = digest_password; 72 | } 73 | 74 | redsocks { 75 | local_ip = 0.0.0.0; 76 | local_port = 12347; 77 | ip = 10.0.1.9; 78 | port = 8081; 79 | type = http-connect; 80 | login = digest_user; 81 | password = buzzword; 82 | } 83 | 84 | redsocks { 85 | local_ip = 0.0.0.0; 86 | local_port = 12348; 87 | ip = 10.0.1.181; 88 | port = 1081; 89 | type = socks5; 90 | } 91 | 92 | redsocks { 93 | local_ip = 0.0.0.0; 94 | local_port = 12349; 95 | ip = 10.0.1.181; 96 | port = 1081; 97 | type = socks5; 98 | login = luser; 99 | password = sockpwd; 100 | } 101 | 102 | redsocks { 103 | local_ip = 0.0.0.0; 104 | local_port = 12350; 105 | ip = 10.0.1.181; 106 | port = 1081; 107 | type = socks5; 108 | login = sockusr; 109 | password = buzzword; 110 | } 111 | 112 | redsocks { 113 | local_ip = 0.0.0.0; 114 | local_port = 12351; 115 | ip = 10.0.1.9; 116 | port = 8081; 117 | type = http-connect; 118 | on_proxy_fail = forward_http_err; 119 | } 120 | 121 | redsocks { 122 | local_ip = 0.0.0.0; 123 | local_port = 12352; 124 | ip = 10.0.1.9; 125 | port = 8081; 126 | type = http-connect; 127 | login = luser; 128 | password = digest_password; 129 | on_proxy_fail = forward_http_err; 130 | } 131 | 132 | redsocks { 133 | local_ip = 0.0.0.0; 134 | local_port = 12353; 135 | ip = 10.0.1.9; 136 | port = 8081; 137 | type = http-connect; 138 | login = digest_user; 139 | password = buzzword; 140 | on_proxy_fail = forward_http_err; 141 | } 142 | 143 | redsocks { 144 | local_ip = 0.0.0.0; 145 | local_port = 12354; 146 | ip = 10.0.1.9; 147 | port = 8081; 148 | type = http-connect; 149 | login = digest_user; 150 | password = digest_password; 151 | on_proxy_fail = forward_http_err; 152 | } 153 | 154 | redsocks { 155 | local_ip = 0.0.0.0; 156 | local_port = 12355; 157 | ip = 10.0.1.9; 158 | port = 1; 159 | type = http-connect; 160 | on_proxy_fail = forward_http_err; 161 | } 162 | 163 | redsocks { 164 | local_ip = 0.0.0.0; 165 | local_port = 12356; 166 | ip = 10.0.1.66; 167 | port = 66; 168 | type = http-connect; 169 | on_proxy_fail = forward_http_err; 170 | } 171 | -------------------------------------------------------------------------------- /tests/run: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | exec py.test -v "$@" 4 | -------------------------------------------------------------------------------- /tests/squid/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:14.04 2 | 3 | RUN set -o xtrace \ 4 | && sed -i 's,^deb-src,# no src # &,; s,http://archive.ubuntu.com/ubuntu/,mirror://mirrors.ubuntu.com/mirrors.txt,' /etc/apt/sources.list \ 5 | && apt-get update \ 6 | && apt-get install -y squid3 7 | 8 | COPY squid-?.conf basic.passwd digest.passwd /etc/squid3/ 9 | 10 | # that's from /etc/init/squid3.conf 11 | ENTRYPOINT ["/usr/sbin/squid3", "-NYC", "-f"] 12 | CMD ["/etc/squid3/squid-8.conf"] 13 | -------------------------------------------------------------------------------- /tests/squid/basic.passwd: -------------------------------------------------------------------------------- 1 | basic_user:$apr1$bslox4us$3RXJauJbX8riqzVzzOYc70 2 | -------------------------------------------------------------------------------- /tests/squid/digest.passwd: -------------------------------------------------------------------------------- 1 | digest_user:digest_password 2 | -------------------------------------------------------------------------------- /tests/squid/squid-8.conf: -------------------------------------------------------------------------------- 1 | auth_param basic program /usr/lib/squid3/basic_ncsa_auth /etc/squid3/basic.passwd 2 | auth_param basic children 5 startup=0 idle=1 3 | auth_param basic realm Squid proxy-caching web server 4 | auth_param basic credentialsttl 2 hours 5 | 6 | acl good proxy_auth basic_user 7 | 8 | # same conf after the line 9 | 10 | acl portopen localport 8080 11 | acl portauth localport 8081 12 | 13 | acl regw src 10.0.8.123/32 14 | 15 | http_access deny manager 16 | http_access deny to_localhost 17 | http_access allow regw portopen 18 | http_access allow regw portauth good 19 | http_access deny all 20 | 21 | http_port 0.0.0.0:8080 22 | http_port 0.0.0.0:8081 23 | 24 | #http_port 0.0.0.0:8082 25 | 26 | #ssl_bump splice all 27 | # http_port [::]:8080 28 | 29 | #http_port 127.0.0.1:12345 intercept 30 | #http_port [::1]:12345 intercept 31 | 32 | #https_port 127.0.0.1:12346 intercept ssl-bump cert=/etc/ssl/certs/ssl-cert-snakeoil.pem key=/etc/ssl/private/ssl-cert-snakeoil.key 33 | #https_port [::1]:12346 intercept ssl-bump cert=/etc/ssl/certs/ssl-cert-snakeoil.pem key=/etc/ssl/private/ssl-cert-snakeoil.key 34 | 35 | #cache_peer 80.80.80.80 parent 8080 0 no-digest no-netdb-exchange proxy-only 36 | #cache_peer_access 80.80.80.80 allow all 37 | #never_direct allow all 38 | 39 | forwarded_for delete 40 | via off 41 | reply_header_access X-Cache-Lookup deny all 42 | reply_header_access X-Squid-Error deny all 43 | reply_header_access X-Cache deny all 44 | request_header_access Cache-Control deny all 45 | cache deny all 46 | memory_pools off 47 | -------------------------------------------------------------------------------- /tests/squid/squid-9.conf: -------------------------------------------------------------------------------- 1 | auth_param digest program /usr/lib/squid3/digest_file_auth /etc/squid3/digest.passwd 2 | auth_param digest children 5 startup=0 idle=1 3 | auth_param digest realm Squid proxy-caching web server 4 | auth_param digest nonce_garbage_interval 5 minutes 5 | auth_param digest nonce_max_duration 30 minutes 6 | auth_param digest nonce_max_count 50 7 | 8 | acl good proxy_auth digest_user 9 | 10 | # same conf after the line 11 | 12 | acl portopen localport 8080 13 | acl portauth localport 8081 14 | 15 | acl regw src 10.0.8.123/32 16 | 17 | http_access deny manager 18 | http_access deny to_localhost 19 | http_access allow regw portopen 20 | http_access allow regw portauth good 21 | http_access deny all 22 | 23 | http_port 0.0.0.0:8080 24 | http_port 0.0.0.0:8081 25 | 26 | #http_port 0.0.0.0:8082 27 | 28 | #ssl_bump splice all 29 | # http_port [::]:8080 30 | 31 | #http_port 127.0.0.1:12345 intercept 32 | #http_port [::1]:12345 intercept 33 | 34 | #https_port 127.0.0.1:12346 intercept ssl-bump cert=/etc/ssl/certs/ssl-cert-snakeoil.pem key=/etc/ssl/private/ssl-cert-snakeoil.key 35 | #https_port [::1]:12346 intercept ssl-bump cert=/etc/ssl/certs/ssl-cert-snakeoil.pem key=/etc/ssl/private/ssl-cert-snakeoil.key 36 | 37 | #cache_peer 80.80.80.80 parent 8080 0 no-digest no-netdb-exchange proxy-only 38 | #cache_peer_access 80.80.80.80 allow all 39 | #never_direct allow all 40 | 41 | forwarded_for delete 42 | via off 43 | reply_header_access X-Cache-Lookup deny all 44 | reply_header_access X-Squid-Error deny all 45 | reply_header_access X-Cache deny all 46 | request_header_access Cache-Control deny all 47 | cache deny all 48 | memory_pools off 49 | -------------------------------------------------------------------------------- /tests/tank/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:14.04 2 | 3 | RUN set -o xtrace \ 4 | && sed -i 's,^deb-src,# no src # &,; s,http://archive.ubuntu.com/ubuntu/,mirror://mirrors.ubuntu.com/mirrors.txt,' /etc/apt/sources.list \ 5 | && apt-get update \ 6 | && apt-get install -y apache2-utils curl pv python-tornado 7 | 8 | RUN set -o xtrace \ 9 | && apt-get install -y iperf 10 | 11 | COPY benchmark.py /benchmark.py 12 | 13 | # yandex-tank is nice `ab' replacement, but it's PPA is broken at the moment 14 | # https://github.com/yandex/yandex-tank/issues/202 15 | # 16 | # apt-get install -y software-properties-common 17 | # add-apt-repository ppa:yandex-load/main 18 | # apt-get install -y phantom phantom-ssl yandex-tank 19 | -------------------------------------------------------------------------------- /tests/tank/benchmark.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2 2 | 3 | import argparse 4 | import functools 5 | import socket 6 | import logging 7 | import json 8 | import time 9 | import datetime 10 | import sys 11 | 12 | import tornado.ioloop as ioloop 13 | import tornado.iostream as iostream 14 | import tornado.httpclient as httpclient 15 | 16 | log = logging.getLogger('benchmark') 17 | 18 | conlist = [] 19 | stats = [] 20 | 21 | DELAY = 0.1 22 | 23 | class Mode(object): 24 | def __init__(self, opt): 25 | self.opt = opt 26 | conlist.append(self) 27 | log.debug('%d of %d', len(conlist), opt.stop) 28 | self.start() 29 | def grab_stats(self): 30 | if self.opt.json is not None: 31 | client = httpclient.AsyncHTTPClient() 32 | self.begin_stats = time.time() 33 | client.fetch('http://%s/debug/meminfo.json' % self.opt.json, self.parse_stats) 34 | else: 35 | self.schedule_next() 36 | def parse_stats(self, response): 37 | log.debug('got stats') 38 | self.end_stats = time.time() 39 | sdict = json.loads(response.body) 40 | sdict['begin'] = self.begin_stats 41 | sdict['end'] = self.end_stats 42 | sdict['conns'] = len(conlist) 43 | stats.append(sdict) 44 | self.schedule_next() 45 | def schedule_next(self): 46 | if len(conlist) < self.opt.stop: 47 | ioloop.IOLoop.current().add_timeout(datetime.timedelta(seconds=DELAY), functools.partial(self.__class__, self.opt)) 48 | else: 49 | ioloop.IOLoop.current().stop() 50 | 51 | class Pipe(Mode): 52 | def start(self): 53 | client = httpclient.AsyncHTTPClient() 54 | self.begin_stats = time.time() 55 | client.fetch('http://%s/debug/pipe' % self.opt.json, self.on_pipe) 56 | def on_pipe(self, response): 57 | self.grab_stats() 58 | 59 | class Connector(Mode): 60 | def start(self): 61 | fd = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) 62 | self.stream = iostream.IOStream(fd) 63 | self.stream.connect((opt.host, opt.port), self.on_connect) 64 | 65 | class EchoConnector(Connector): 66 | ECHO = 'ECHO\x0d\x0a' 67 | def on_connect(self): 68 | self.stream.write(self.ECHO) 69 | self.stream.read_bytes(len(self.ECHO), self.on_pong) 70 | def on_pong(self, data): 71 | assert data == self.ECHO 72 | self.grab_stats() 73 | 74 | class ChargenConnector(Connector): 75 | def __init__(self, opt): 76 | Connector.__init__(self, opt) 77 | self.gotstats = False 78 | def on_connect(self): 79 | self.stream.read_until_close(self.on_close, self.on_datachunk) 80 | def on_close(self, data): 81 | pass 82 | def on_datachunk(self, data): 83 | if not self.gotstats: 84 | self.gotstats = True 85 | self.grab_stats() 86 | 87 | MODES = { 88 | 'echo': EchoConnector, 89 | 'chargen': ChargenConnector, 90 | 'pipe': Pipe, 91 | } 92 | 93 | def parse_cmd(argv): 94 | parser = argparse.ArgumentParser(prog=argv[0], description='/ping handler') 95 | parser.add_argument('--json', type=str) 96 | parser.add_argument('--stop', default=10, type=int) 97 | parser.add_argument('--verbose', default=False, action='store_true') 98 | parser.add_argument('--mode', metavar='MODE', required=True, choices=sorted(MODES)) 99 | parser.add_argument('--port', metavar='PORT', type=int, default=None) 100 | parser.add_argument('host', metavar='HOST', type=str) 101 | if len(sys.argv) == 1: 102 | sys.argv.append('-h') 103 | opt = parser.parse_args(argv[1:]) 104 | if opt.port is None: 105 | opt.port = {'echo': 7, 'chargen': 19, 'pipe': -1}[opt.mode] 106 | return opt 107 | 108 | def main(opt): 109 | LOGGING_FORMAT = '%(asctime)s %(name)s[%(process)d] %(levelname)s: %(message)s' 110 | if not opt.verbose: 111 | logging.basicConfig(level=logging.INFO, format=LOGGING_FORMAT) 112 | logging.getLogger('tornado.access').setLevel(logging.INFO+1) 113 | else: 114 | logging.basicConfig(level=logging.DEBUG, format=LOGGING_FORMAT) 115 | 116 | klass = MODES[opt.mode] 117 | klass(opt) 118 | 119 | try: 120 | ioloop.IOLoop.instance().start() 121 | except KeyboardInterrupt: 122 | pass 123 | finally: 124 | log.fatal('Done.') 125 | print json.dumps(stats) 126 | 127 | if __name__ == '__main__': 128 | main(parse_cmd(sys.argv)) 129 | -------------------------------------------------------------------------------- /tests/test_smoke.py: -------------------------------------------------------------------------------- 1 | from functools import partial 2 | from subprocess import check_call, CalledProcessError, Popen, PIPE 3 | import time 4 | 5 | import conftest 6 | import pytest 7 | 8 | @pytest.mark.skipif(not pytest.config.getoption('--vmdebug'), reason='need --vmdebug option to run') 9 | def test_vmdebug(net): 10 | check_call('sleep 365d'.split()) 11 | 12 | GOOD_AUTH = 'connect_none connect_basic connect_digest socks5_none socks5_auth httperr_connect_digest'.split() 13 | BAD_AUTH = 'connect_nopass connect_baduser connect_badpass socks5_nopass socks5_baduser socks5_badpass httperr_proxy_refuse'.split() 14 | UGLY_AUTH = 'httperr_connect_nopass httperr_connect_baduser httperr_connect_badpass'.split() 15 | assert set(conftest.TANKS) == set(GOOD_AUTH + BAD_AUTH + UGLY_AUTH + ['httperr_proxy_timeout']) 16 | 17 | @pytest.mark.parametrize('tank', GOOD_AUTH) 18 | def test_smoke(net, tank): 19 | vm = net.vm['tank%d' % conftest.TANKS[tank]] 20 | page = vm.do('curl --max-time 0.5 http://10.0.1.80/') 21 | assert 'Welcome to nginx!' in page 22 | 23 | @pytest.mark.parametrize('tank', BAD_AUTH) 24 | def test_badauth(net, tank): 25 | vm = net.vm['tank%d' % conftest.TANKS[tank]] 26 | with pytest.raises(CalledProcessError) as excinfo: 27 | vm.do('curl --max-time 0.5 http://10.0.1.80/') 28 | assert excinfo.value.returncode == 52 # Empty reply from server 29 | 30 | @pytest.mark.parametrize('tank', UGLY_AUTH) 31 | def test_uglyauth(net, tank): 32 | vm = net.vm['tank%d' % conftest.TANKS[tank]] 33 | page = vm.do('curl -sSv --max-time 0.5 http://10.0.1.80/') 34 | assert '' in page 35 | 36 | @pytest.mark.parametrize('tank', set(conftest.TANKS) - set(UGLY_AUTH + ['httperr_connect_digest'])) 37 | def test_econnrefused(net, tank): 38 | vm = net.vm['tank%d' % conftest.TANKS[tank]] 39 | with pytest.raises(CalledProcessError) as excinfo: 40 | vm.do('curl --max-time 0.5 http://10.0.1.80:81/') 41 | if tank == 'httperr_proxy_timeout': 42 | assert excinfo.value.returncode == 28 # Operation timed out 43 | else: 44 | assert excinfo.value.returncode == 52 # Empty reply from server 45 | 46 | def test_econnrefused_httperr(net): 47 | tank = 'httperr_connect_digest' 48 | vm = net.vm['tank%d' % conftest.TANKS[tank]] 49 | page = vm.do('curl --max-time 0.5 http://10.0.1.80:81/') 50 | assert '' in page 51 | 52 | RTT = 200 # ms 53 | 54 | @pytest.fixture(scope="function") 55 | def slow_net(request, net): 56 | def close(): 57 | net.vm['gw'].netcall('tc qdisc del dev ethx root') 58 | net.vm['gw'].netcall('tc qdisc del dev ethw root') 59 | request.addfinalizer(close) 60 | net.vm['gw'].netcall('tc qdisc add dev ethw root netem delay %dms' % (RTT / 2)) 61 | net.vm['gw'].netcall('tc qdisc add dev ethx root netem delay %dms' % (RTT / 2)) 62 | return net 63 | 64 | LATENCY = { 65 | 'connect_none': 3 * RTT, 66 | 'connect_basic': 3 * RTT, 67 | 'connect_digest': 3 * RTT, 68 | 'socks5_none': 4 * RTT, 69 | 'socks5_auth': 5 * RTT, 70 | 'regw_direct': 2 * RTT, 71 | } 72 | 73 | def heatup(vm): 74 | vm.do('curl -o /dev/null http://10.0.1.80/') # heatup L2 and auth caches 75 | 76 | def http_ping(vm): 77 | s = vm.do('curl -sS -w %{{time_connect}}/%{{time_total}}/%{{http_code}}/%{{size_download}} -o /dev/null http://10.0.1.80/') 78 | connect, total, code, size = s.split('/') 79 | connect, total, code, size = float(connect) * 1000, float(total) * 1000, int(code), int(size) 80 | return connect, total, code, size 81 | 82 | @pytest.mark.parametrize('tank', set(conftest.TANKS) & set(LATENCY)) 83 | def test_latency_tank(slow_net, tank): 84 | vm = slow_net.vm['tank%d' % conftest.TANKS[tank]] 85 | heatup(vm) 86 | connect, total, code, size = http_ping(vm) 87 | assert code == 200 and size == 612 88 | assert connect < 0.005 and LATENCY[tank]-RTT*.2 < total and total < LATENCY[tank]+RTT*.2 89 | 90 | def test_latency_regw(slow_net): 91 | vm, tank = slow_net.vm['regw'], 'regw_direct' 92 | heatup(vm) 93 | connect, total, code, size = http_ping(vm) 94 | assert code == 200 and size == 612 95 | assert RTT*.8 < connect and connect < RTT*1.2 and LATENCY[tank]-RTT*.2 < total and total < LATENCY[tank]+RTT*.2 96 | 97 | def test_nonce_reuse(slow_net): 98 | """ nonce reuse works and has no latency penalty """ 99 | tank = 'connect_digest' 100 | vm = slow_net.vm['tank%d' % conftest.TANKS[tank]] 101 | heatup(vm) 102 | begin = time.time() 103 | s = conftest.pmap([partial(http_ping, vm) for _ in range(5)]) 104 | total_sum = time.time() - begin 105 | for connect, total, code, size in s: 106 | assert code == 200 and size == 612 107 | assert connect < 0.005 and LATENCY[tank]-RTT*.2 < total and total < LATENCY[tank]+RTT*.2 108 | assert total_sum < total * 1.5 109 | 110 | @pytest.mark.parametrize('tank, delay', [(t, d) 111 | for t in set(conftest.TANKS) & set(LATENCY) 112 | for d in [_*0.001 for _ in range(1, LATENCY[t]+RTT, RTT/2)] 113 | ] + [ 114 | ('httperr_proxy_refuse', (1 + 0*RTT/2) * 0.001), 115 | ('httperr_proxy_refuse', (1 + 1*RTT/2) * 0.001), 116 | ('httperr_proxy_refuse', (1 + 2*RTT/2) * 0.001), 117 | ('httperr_proxy_refuse', (1 + 3*RTT/2) * 0.001), 118 | ('httperr_proxy_timeout', (1 + 3*RTT/2) * 0.001), 119 | ]) 120 | def test_impatient_client(slow_net, tank, delay): 121 | vm, regw = slow_net.vm['tank%d' % conftest.TANKS[tank]], slow_net.vm['regw'] 122 | before, start = regw.lsfd(), time.time() 123 | try: 124 | page = vm.do('curl --max-time %s http://10.0.1.80/1M' % delay) 125 | #assert 'Welcome to nginx!' in page 126 | except CalledProcessError, e: 127 | if tank == 'httperr_proxy_refuse': 128 | assert e.returncode in (28, 52) # Operation timeout / Empty reply 129 | else: 130 | assert e.returncode == 28 # Operation timeout 131 | curl_time = time.time() - start 132 | assert curl_time < delay + RTT*0.001 # sanity check 133 | time.sleep( (LATENCY.get(tank, delay*1000) + 4*RTT)*0.001 ) 134 | if tank == 'httperr_proxy_timeout': 135 | time.sleep(135) # default connect() timeout is long 136 | assert before == regw.lsfd() # no leaks 137 | 138 | @pytest.fixture(scope='function', params=range(80, 60, -1)) 139 | def lowfd_net(request, net): 140 | def close(): 141 | net.vm['regw'].call('sudo ./prlimit-nofile {pid} 1024') 142 | request.addfinalizer(close) 143 | net.vm['regw'].call('sudo ./prlimit-nofile {pid} %d' % request.param) 144 | return net 145 | 146 | def test_accept_overflow(lowfd_net): 147 | tank = 'connect_none' 148 | vm, regw = lowfd_net.vm['tank%d' % conftest.TANKS[tank]], lowfd_net.vm['regw'] 149 | lsfd = regw.lsfd() 150 | year = str(time.gmtime().tm_year) 151 | discard_cmd = vm.fmt('sudo docker exec --interactive {sha} nc 10.0.1.13 discard') 152 | daytime_cmd = vm.fmt('sudo docker exec --interactive {sha} nc 10.0.1.13 daytime') 153 | 154 | dtstart = time.time() 155 | time.sleep(0.5) 156 | proc = [Popen(discard_cmd, stdin=PIPE, stdout=PIPE) for i in xrange(7)] 157 | time.sleep(0.5) 158 | dt = Popen(daytime_cmd, stdin=PIPE, stdout=PIPE) 159 | time.sleep(0.5) 160 | logs = regw.logs(since=dtstart) 161 | if any(['Too many open files' in _[1] for _ in logs]): 162 | # anything may happen, except leaks 163 | proc[0].communicate('/dev/null\x0d\x0a') 164 | dt.communicate('') 165 | time.sleep(1) 166 | for p in proc[1:]: 167 | p.communicate('/dev/null\x0d\x0a') 168 | else: 169 | assert any(['reached redsocks_conn_max limit' in _[1] for _ in logs]) 170 | # RLIMIT_NOFILE was not hit, this `daytime` call actually returns data 171 | daytime, _ = dt.communicate('') 172 | assert year in daytime and dt.returncode == 0 173 | time.sleep(0.5) 174 | dtdeath = time.time() 175 | proc.append(Popen(discard_cmd, stdin=PIPE, stdout=PIPE)) 176 | time.sleep(0.5) 177 | logs = regw.logs(since=dtdeath) 178 | assert any(['reached redsocks_conn_max limit' in _[1] for _ in logs]) 179 | time.sleep(0.5) 180 | dt = Popen(daytime_cmd, stdin=PIPE, stdout=PIPE) 181 | time.sleep(0.5) 182 | assert all([p.poll() is None for p in proc]) and dt.poll() is None # processes are running 183 | out, _ = proc[0].communicate('/dev/null\x0d\x0a') 184 | daytime, _ = dt.communicate('') 185 | assert year in daytime and dt.returncode == 0 186 | time.sleep(1) 187 | for p in proc[1:]: 188 | out, _ = p.communicate('/dev/null\x0d\x0a') 189 | assert out == '' 190 | 191 | time.sleep(1) 192 | assert lsfd == regw.lsfd() # no leaks 193 | -------------------------------------------------------------------------------- /tests/web/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:14.04 2 | 3 | RUN set -o xtrace \ 4 | && sed -i 's,^deb-src,# no src # &,; s,http://archive.ubuntu.com/ubuntu/,mirror://mirrors.ubuntu.com/mirrors.txt,' /etc/apt/sources.list \ 5 | && apt-get update \ 6 | && apt-get install -y nginx-light 7 | 8 | RUN set -o xtrace \ 9 | && dd if=/dev/urandom of=/usr/share/nginx/html/128K count=1 bs=128K \ 10 | && dd if=/dev/urandom of=/usr/share/nginx/html/1M count=1 bs=1M \ 11 | && for i in `seq 16`; do cat /usr/share/nginx/html/1M; done >/usr/share/nginx/html/16M 12 | 13 | CMD ["/usr/sbin/nginx", "-g", "daemon off;"] 14 | -------------------------------------------------------------------------------- /timer.h: -------------------------------------------------------------------------------- 1 | #ifndef TIMER_H_SAT_DEC_17_46_12_07_2019 2 | #define TIMER_H_SAT_DEC_17_46_12_07_2019 3 | 4 | #define Timercmp(tvp, uvp, cmp) \ 5 | (((tvp)->tv_sec == (uvp)->tv_sec) ? \ 6 | ((tvp)->tv_usec cmp (uvp)->tv_usec) : \ 7 | ((tvp)->tv_sec cmp (uvp)->tv_sec)) 8 | #define Timeradd(tvp, uvp, vvp) \ 9 | do { \ 10 | (vvp)->tv_sec = (tvp)->tv_sec + (uvp)->tv_sec; \ 11 | (vvp)->tv_usec = (tvp)->tv_usec + (uvp)->tv_usec; \ 12 | if ((vvp)->tv_usec >= 1000000) { \ 13 | (vvp)->tv_sec++; \ 14 | (vvp)->tv_usec -= 1000000; \ 15 | } \ 16 | } while (0) 17 | #define Timersub(tvp, uvp, vvp) \ 18 | do { \ 19 | (vvp)->tv_sec = (tvp)->tv_sec - (uvp)->tv_sec; \ 20 | (vvp)->tv_usec = (tvp)->tv_usec - (uvp)->tv_usec; \ 21 | if ((vvp)->tv_usec < 0) { \ 22 | (vvp)->tv_sec--; \ 23 | (vvp)->tv_usec += 1000000; \ 24 | } \ 25 | } while (0) 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /tools/git-repack.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -o errexit 4 | set -o xtrace 5 | 6 | user="darkk" 7 | proj="redsocks" 8 | versions="0.1 0.2 0.3 0.4" 9 | 10 | 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 11 | touch "$file.uploaded" 12 | done 13 | 14 | token="" 15 | 16 | for ver in $versions; do 17 | for pkg in tar.gz tar.bz2 tar.lzma; do 18 | case $pkg in 19 | tar.gz) sortof=gzip ;; 20 | tar.bz2) sortof=bzip2 ;; 21 | tar.lzma) sortof=lzma ;; 22 | esac 23 | 24 | tag="release-${ver}" 25 | gittar="github-$tag.tar.gz" 26 | gooddir="${proj}-${ver}" 27 | file="${proj}-${ver}.${pkg}" 28 | 29 | if [ -r "${file}.uploaded" ]; then 30 | continue 31 | fi 32 | 33 | rm -rf "${user}-${proj}-"* 34 | 35 | if [ ! -r "$file" ]; then 36 | if [ ! -d "$gooddir" ]; then 37 | if [ ! -f "$gittar" ]; then 38 | wget -O "$gittar" "https://github.com/${user}/${proj}/tarball/${tag}" 39 | fi 40 | tar --extract --file "$gittar" 41 | mv "${user}-${proj}-"* "$gooddir" 42 | fi 43 | tar "--${sortof}" --create --file "$file" "$gooddir" 44 | fi 45 | 46 | if [ -z "$token" ]; then 47 | if [ ! -r git-repack.sh.token ]; then 48 | curl --data '{"scopes": ["public_repo"], "note": "uploader script"}' -u "$user" "https://api.github.com/authorizations" \ 49 | | python -c "import sys, json, pipes; print 'token=%s' % pipes.quote(json.load(sys.stdin)['token'])" \ 50 | > git-repack.sh.token 51 | fi 52 | . git-repack.sh.token 53 | fi 54 | 55 | . <(python -c "import os, urllib2, json, pipes; print '\n'.join('GITHUB_%s=%s' % (k.replace('-', '_'), pipes.quote(str(v))) for k, v in json.load(urllib2.urlopen('https://api.github.com/repos/${user}/${proj}/downloads?access_token=${token}', data=json.dumps({'name': '${file}', 'size': os.stat('${file}').st_size, 'description': 'Version ${ver}, ${sortof}', 'content-type': 'application/x-gtar-compressed'}))).iteritems())") 56 | curl \ 57 | --include \ 58 | -F "key=${GITHUB_path}" -F "acl=${GITHUB_acl}" -F "success_action_status=201" \ 59 | -F "Filename=${GITHUB_name}" \ 60 | -F "AWSAccessKeyId=${GITHUB_accesskeyid}" -F "Policy=${GITHUB_policy}" -F "Signature=${GITHUB_signature}" \ 61 | -F "Content-Type=${GITHUB_mime_type}" \ 62 | -F "file=@${file}" https://github.s3.amazonaws.com/ 63 | echo; 64 | touch "${file}.uploaded" 65 | done 66 | done 67 | -------------------------------------------------------------------------------- /tsearch.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "tsearch.h" 4 | 5 | static inline int height(struct node *n) { return n ? n->h : 0; } 6 | 7 | static int rot(void **p, struct node *x, int dir /* deeper side */) 8 | { 9 | struct node *y = x->a[dir]; 10 | struct node *z = y->a[!dir]; 11 | int hx = x->h; 12 | int hz = height(z); 13 | if (hz > height(y->a[dir])) { 14 | /* 15 | * x 16 | * / \ dir z 17 | * A y / \ 18 | * / \ --> x y 19 | * z D /| |\ 20 | * / \ A B C D 21 | * B C 22 | */ 23 | x->a[dir] = z->a[!dir]; 24 | y->a[!dir] = z->a[dir]; 25 | z->a[!dir] = x; 26 | z->a[dir] = y; 27 | x->h = hz; 28 | y->h = hz; 29 | z->h = hz+1; 30 | } else { 31 | /* 32 | * x y 33 | * / \ / \ 34 | * A y --> x D 35 | * / \ / \ 36 | * z D A z 37 | */ 38 | x->a[dir] = z; 39 | y->a[!dir] = x; 40 | x->h = hz+1; 41 | y->h = hz+2; 42 | z = y; 43 | } 44 | *p = z; 45 | return z->h - hx; 46 | } 47 | 48 | /* balance *p, return 0 if height is unchanged. */ 49 | int __tsearch_balance(void **p) 50 | { 51 | struct node *n = *p; 52 | int h0 = height(n->a[0]); 53 | int h1 = height(n->a[1]); 54 | if (h0 - h1 + 1u < 3u) { 55 | int old = n->h; 56 | n->h = h0

h - old; 58 | } 59 | return rot(p, n, h0key); 77 | if (!c) 78 | return n; 79 | a[i++] = &n->a[c>0]; 80 | n = n->a[c>0]; 81 | } 82 | r = malloc(sizeof *r); 83 | if (!r) 84 | return 0; 85 | r->key = key; 86 | r->a[0] = r->a[1] = 0; 87 | r->h = 1; 88 | /* insert new node, rebalance ancestors. */ 89 | *a[--i] = r; 90 | while (i && __tsearch_balance(a[--i])); 91 | return r; 92 | } 93 | -------------------------------------------------------------------------------- /tsearch.h: -------------------------------------------------------------------------------- 1 | #ifndef TSEARCH_H_SAT_DEC_17_46_12_07_2019 2 | #define TSEARCH_H_SAT_DEC_17_46_12_07_2019 3 | 4 | #include 5 | //#include 6 | 7 | /* AVL tree height < 1.44*log2(nodes+2)-0.3, MAXH is a safe upper bound. */ 8 | #define MAXH (sizeof(void*)*8*3/2) 9 | 10 | struct node { 11 | const void *key; 12 | void *a[2]; 13 | int h; 14 | }; 15 | 16 | int __tsearch_balance(void **); 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /utils.c: -------------------------------------------------------------------------------- 1 | /* redsocks - transparent TCP-to-proxy redirector 2 | * Copyright (C) 2007-2018 Leonid Evdokimov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include "log.h" 26 | #include "base.h" 27 | #include "utils.h" 28 | #include "redsocks.h" // for redsocks_close 29 | #include "libc-compat.h" 30 | 31 | int red_recv_udp_pkt(int fd, char *buf, size_t buflen, struct sockaddr_in *inaddr, struct sockaddr_in *toaddr) 32 | { 33 | socklen_t addrlen = sizeof(*inaddr); 34 | ssize_t pktlen; 35 | struct msghdr msg; 36 | struct iovec io; 37 | char control[1024]; 38 | 39 | memset(&msg, 0, sizeof(msg)); 40 | msg.msg_name = inaddr; 41 | msg.msg_namelen = sizeof(*inaddr); 42 | msg.msg_iov = &io; 43 | msg.msg_iovlen = 1; 44 | msg.msg_control = control; 45 | msg.msg_controllen = sizeof(control); 46 | io.iov_base = buf; 47 | io.iov_len = buflen; 48 | 49 | pktlen = recvmsg(fd, &msg, 0); 50 | if (pktlen == -1) { 51 | log_errno(LOG_WARNING, "recvfrom"); 52 | return -1; 53 | } 54 | 55 | if (toaddr) { 56 | memset(toaddr, 0, sizeof(*toaddr)); 57 | for (struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { 58 | if ( 59 | cmsg->cmsg_level == SOL_IP && 60 | cmsg->cmsg_type == IP_ORIGDSTADDR && 61 | cmsg->cmsg_len >= CMSG_LEN(sizeof(*toaddr)) 62 | ) { 63 | struct sockaddr_in* cmsgaddr = (struct sockaddr_in*)CMSG_DATA(cmsg); 64 | char buf[RED_INET_ADDRSTRLEN]; 65 | log_error(LOG_DEBUG, "IP_ORIGDSTADDR: %s", red_inet_ntop(cmsgaddr, buf, sizeof(buf))); 66 | memcpy(toaddr, cmsgaddr, sizeof(*toaddr)); 67 | } 68 | else { 69 | log_error(LOG_WARNING, "unexepcted cmsg (level,type) = (%d,%d)", 70 | cmsg->cmsg_level, cmsg->cmsg_type); 71 | } 72 | } 73 | if (toaddr->sin_family != AF_INET) { 74 | log_error(LOG_WARNING, "(SOL_IP, IP_ORIGDSTADDR) not found"); 75 | return -1; 76 | } 77 | } 78 | 79 | if (addrlen != sizeof(*inaddr)) { 80 | log_error(LOG_WARNING, "unexpected address length %u instead of %zu", addrlen, sizeof(*inaddr)); 81 | return -1; 82 | } 83 | 84 | if (pktlen >= buflen) { 85 | char buf[RED_INET_ADDRSTRLEN]; 86 | log_error(LOG_WARNING, "wow! Truncated udp packet of size %zd from %s! impossible! dropping it...", 87 | pktlen, red_inet_ntop(inaddr, buf, sizeof(buf))); 88 | return -1; 89 | } 90 | 91 | return pktlen; 92 | } 93 | 94 | uint32_t red_randui32() 95 | { 96 | uint32_t ret; 97 | evutil_secure_rng_get_bytes(&ret, sizeof(ret)); 98 | return ret; 99 | } 100 | 101 | time_t redsocks_time(time_t *t) 102 | { 103 | time_t retval; 104 | retval = time(t); 105 | if (retval == ((time_t) -1)) 106 | log_errno(LOG_WARNING, "time"); 107 | return retval; 108 | } 109 | 110 | int redsocks_gettimeofday(struct timeval *tv) 111 | { 112 | int retval = gettimeofday(tv, NULL); 113 | if (retval != 0) 114 | log_errno(LOG_WARNING, "gettimeofday"); 115 | return retval; 116 | } 117 | 118 | char *redsocks_evbuffer_readline(struct evbuffer *buf) 119 | { 120 | #if _EVENT_NUMERIC_VERSION >= 0x02000000 121 | return evbuffer_readln(buf, NULL, EVBUFFER_EOL_CRLF); 122 | #else 123 | return evbuffer_readline(buf); 124 | #endif 125 | } 126 | 127 | int red_socket_client(int type) 128 | { 129 | int fd = -1; 130 | int error; 131 | 132 | fd = socket(AF_INET, type, 0); 133 | if (fd == -1) { 134 | log_errno(LOG_ERR, "socket"); 135 | goto fail; 136 | } 137 | 138 | error = fcntl_nonblock(fd); 139 | if (error) { 140 | log_errno(LOG_ERR, "fcntl"); 141 | goto fail; 142 | } 143 | 144 | if (type == SOCK_STREAM) { 145 | if (apply_tcp_keepalive(fd)) 146 | goto fail; 147 | } 148 | 149 | return fd; 150 | 151 | fail: 152 | if (fd != -1) 153 | redsocks_close(fd); 154 | return -1; 155 | } 156 | 157 | int red_socket_server(int type, struct sockaddr_in *bindaddr) 158 | { 159 | int on = 1; 160 | int error; 161 | int fd = red_socket_client(type); 162 | if (fd == -1) 163 | goto fail; 164 | 165 | error = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); 166 | if (error) { 167 | log_errno(LOG_ERR, "setsockopt"); 168 | goto fail; 169 | } 170 | 171 | error = bind(fd, (struct sockaddr*)bindaddr, sizeof(*bindaddr)); 172 | if (error) { 173 | log_errno(LOG_ERR, "bind"); 174 | goto fail; 175 | } 176 | 177 | return fd; 178 | fail: 179 | if (fd != -1) 180 | redsocks_close(fd); 181 | return -1; 182 | 183 | } 184 | 185 | 186 | struct bufferevent* red_connect_relay(struct sockaddr_in *addr, evbuffercb writecb, everrorcb errorcb, void *cbarg) 187 | { 188 | struct bufferevent *retval = NULL; 189 | int relay_fd = -1; 190 | int error; 191 | 192 | relay_fd = red_socket_client(SOCK_STREAM); 193 | 194 | error = connect(relay_fd, (struct sockaddr*)addr, sizeof(*addr)); 195 | if (error && errno != EINPROGRESS) { 196 | log_errno(LOG_NOTICE, "connect"); 197 | goto fail; 198 | } 199 | 200 | retval = bufferevent_new(relay_fd, NULL, writecb, errorcb, cbarg); 201 | if (!retval) { 202 | log_errno(LOG_ERR, "bufferevent_new"); 203 | goto fail; 204 | } 205 | 206 | relay_fd = -1; 207 | 208 | error = bufferevent_enable(retval, EV_WRITE); // we wait for connection... 209 | if (error) { 210 | log_errno(LOG_ERR, "bufferevent_enable"); 211 | goto fail; 212 | } 213 | 214 | return retval; 215 | 216 | fail: 217 | if (relay_fd != -1) 218 | redsocks_close(relay_fd); 219 | if (retval) 220 | redsocks_bufferevent_free(retval); 221 | return NULL; 222 | } 223 | 224 | int red_socket_geterrno(struct bufferevent *buffev) 225 | { 226 | int error; 227 | int pseudo_errno; 228 | socklen_t optlen = sizeof(pseudo_errno); 229 | 230 | assert(event_get_fd(&buffev->ev_read) == event_get_fd(&buffev->ev_write)); 231 | 232 | error = getsockopt(event_get_fd(&buffev->ev_read), SOL_SOCKET, SO_ERROR, &pseudo_errno, &optlen); 233 | if (error) { 234 | log_errno(LOG_ERR, "getsockopt"); 235 | return -1; 236 | } 237 | return pseudo_errno; 238 | } 239 | 240 | /** simple fcntl(2) wrapper, provides errno and all logging to caller 241 | * I have to use it in event-driven code because of accept(2) (see NOTES) 242 | * and connect(2) (see ERRORS about EINPROGRESS) 243 | */ 244 | int fcntl_nonblock(int fd) 245 | { 246 | int error; 247 | int flags; 248 | 249 | flags = fcntl(fd, F_GETFL); 250 | if (flags == -1) 251 | return -1; 252 | 253 | error = fcntl(fd, F_SETFL, flags | O_NONBLOCK); 254 | if (error) 255 | return -1; 256 | 257 | return 0; 258 | } 259 | 260 | int red_is_socket_connected_ok(struct bufferevent *buffev) 261 | { 262 | int pseudo_errno = red_socket_geterrno(buffev); 263 | 264 | if (pseudo_errno == -1) { 265 | return 0; 266 | } 267 | else if (pseudo_errno) { 268 | errno = pseudo_errno; 269 | log_errno(LOG_NOTICE, "connect"); 270 | return 0; 271 | } 272 | else { 273 | return 1; 274 | } 275 | } 276 | 277 | char *red_inet_ntop(const struct sockaddr_in* sa, char* buffer, size_t buffer_size) 278 | { 279 | const char *retval = 0; 280 | size_t len = 0; 281 | uint16_t port; 282 | const char placeholder[] = "???:???"; 283 | 284 | assert(buffer_size >= RED_INET_ADDRSTRLEN); 285 | 286 | memset(buffer, 0, buffer_size); 287 | if (sa->sin_family == AF_INET) { 288 | retval = inet_ntop(AF_INET, &sa->sin_addr, buffer, buffer_size); 289 | port = ((struct sockaddr_in*)sa)->sin_port; 290 | } 291 | else if (sa->sin_family == AF_INET6) { 292 | retval = inet_ntop(AF_INET6, &((const struct sockaddr_in6*)sa)->sin6_addr, buffer, buffer_size); 293 | port = ((struct sockaddr_in6*)sa)->sin6_port; 294 | } 295 | if (retval) { 296 | assert(retval == buffer); 297 | len = strlen(retval); 298 | snprintf(buffer + len, buffer_size - len, ":%d", ntohs(port)); 299 | } 300 | else { 301 | strcpy(buffer, placeholder); 302 | } 303 | return buffer; 304 | } 305 | 306 | /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ 307 | -------------------------------------------------------------------------------- /utils.h: -------------------------------------------------------------------------------- 1 | #ifndef UTILS_H_SAT_FEB__2_02_24_05_2008 2 | #define UTILS_H_SAT_FEB__2_02_24_05_2008 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | struct sockaddr_in; 9 | 10 | #define SIZEOF_ARRAY(arr) (sizeof(arr) / sizeof(arr[0])) 11 | #define FOREACH(ptr, array) for (ptr = array; ptr < array + SIZEOF_ARRAY(array); ptr++) 12 | #define FOREACH_REV(ptr, array) for (ptr = array + SIZEOF_ARRAY(array) - 1; ptr >= array; ptr--) 13 | 14 | #define UNUSED(x) ((void)(x)) 15 | 16 | #if defined __GNUC__ 17 | #define PACKED __attribute__((packed)) 18 | #else 19 | #error Unknown compiler, modify utils.h for it 20 | #endif 21 | 22 | 23 | #ifdef __GNUC__ 24 | #define member_type(type, member) __typeof(((type *)0)->member) 25 | #else 26 | #define member_type(type, member) const void 27 | #endif 28 | 29 | /** 30 | * container_of - cast a member of a structure out to the containing structure 31 | * @ptr: the pointer to the member. 32 | * @type: the type of the container struct this is embedded in. 33 | * @member: the name of the member within the struct. 34 | * 35 | */ 36 | #define container_of(ptr, type, member) \ 37 | ((type *)( \ 38 | (char *)(member_type(type, member) *){ ptr } - offsetof(type, member) \ 39 | )) 40 | 41 | 42 | #define clamp_value(value, min_val, max_val) do { \ 43 | if (value < min_val) \ 44 | value = min_val; \ 45 | if (value > max_val) \ 46 | value = max_val; \ 47 | } while (0) 48 | 49 | 50 | uint32_t red_randui32(); 51 | time_t redsocks_time(time_t *t); 52 | int redsocks_gettimeofday(struct timeval *tv); 53 | char *redsocks_evbuffer_readline(struct evbuffer *buf); 54 | struct bufferevent* red_connect_relay(struct sockaddr_in *addr, evbuffercb writecb, everrorcb errorcb, void *cbarg); 55 | int red_socket_geterrno(struct bufferevent *buffev); 56 | int red_socket_client(int type); 57 | int red_socket_server(int type, struct sockaddr_in *bindaddr); 58 | int red_is_socket_connected_ok(struct bufferevent *buffev); 59 | int red_recv_udp_pkt(int fd, char *buf, size_t buflen, struct sockaddr_in *fromaddr, struct sockaddr_in *toaddr); 60 | 61 | int fcntl_nonblock(int fd); 62 | 63 | #define event_fmt_str "%s|%s|%s|%s|%s|0x%x" 64 | #define event_fmt(what) \ 65 | (what) & EVBUFFER_READ ? "EVBUFFER_READ" : "0", \ 66 | (what) & EVBUFFER_WRITE ? "EVBUFFER_WRITE" : "0", \ 67 | (what) & EVBUFFER_EOF ? "EVBUFFER_EOF" : "0", \ 68 | (what) & EVBUFFER_ERROR ? "EVBUFFER_ERROR" : "0", \ 69 | (what) & EVBUFFER_TIMEOUT ? "EVBUFFER_TIMEOUT" : "0", \ 70 | (what) & ~(EVBUFFER_READ|EVBUFFER_WRITE|EVBUFFER_EOF|EVBUFFER_ERROR|EVBUFFER_TIMEOUT) 71 | 72 | #if INET6_ADDRSTRLEN < INET_ADDRSTRLEN 73 | # error Impossible happens: INET6_ADDRSTRLEN < INET_ADDRSTRLEN 74 | #else 75 | # define RED_INET_ADDRSTRLEN (INET6_ADDRSTRLEN + 1 + 5 + 1) // addr + : + port + \0 76 | #endif 77 | char *red_inet_ntop(const struct sockaddr_in* sa, char* buffer, size_t buffer_size); 78 | 79 | /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ 80 | /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ 81 | #endif /* UTILS_H_SAT_FEB__2_02_24_05_2008 */ 82 | -------------------------------------------------------------------------------- /version.h: -------------------------------------------------------------------------------- 1 | #ifndef VERSION_H_SUN_NOV_27_03_22_30_2011 2 | #define VERSION_H_SUN_NOV_27_03_22_30_2011 3 | 4 | extern const char* redsocks_version; 5 | 6 | #endif // VERSION_H_SUN_NOV_27_03_22_30_2011 7 | --------------------------------------------------------------------------------