first commit

This commit is contained in:
bolvan 2016-02-15 16:34:45 +03:00
commit 7443de517a
29 changed files with 9416 additions and 0 deletions

24
changes.txt Normal file
View File

@ -0,0 +1,24 @@
v1
Initial release
v2
nfqws : command line options change. now using standard getopt.
nfqws : added options for window size changing and "Host:" case change
ISP support : tested on mns.ru and beeline (corbina)
init scripts : rewritten init scripts for simple choise of ISP
create_ipset : now using 'ipset restore', it works much faster
readme : updated. now using UTF-8 charset.
v3
tpws : added transparent proxy (supports TPROXY and DNAT).
can help when ISP tracks whole HTTP session, not only the beginning
ipset : added zapret-hosts-user.txt which contain user defined host names to be resolved
and added to zapret ip list
ISP support : dom.ru support via TPROXY/DNAT
ISP support : successfully tested sknt.ru on 'domru' configuration
other configs will probably also work, but cannot test
compile : openwrt compile howto

View File

@ -0,0 +1,38 @@
How to compile native programs for use in openwrt
-------------------------------------------------
1) <fetch correct version of openwrt>
cd ~
<chaos calmer>
git clone git://git.openwrt.org/15.05/openwrt.git
<barrier breaker>
git clone git://git.openwrt.org/14.07/openwrt.git
<trunk>
git clone git://git.openwrt.org/openwrt.git
cd openwrt
2) ./scripts/feeds update -a
./scripts/feeds install -a
3) <add zapret packages to build root>
<copy package descriptions>
copy compile/openwrt/* to ~/openwrt
<copy source code of tpws>
copy tpws to ~/openwrt/package/zapret/tpws
<copy source code of nfq>
copy nfq to ~/openwrt/package/zapret/nfq
4) make menuconfig
<select your target architecture>
<select packages Network/Zapret/* as "M">
5) make toolchain/compile
6) make package/tpws/compile
make package/nfqws/compile
7) find bin -name tpws*.ipk
<take your tpws*.ipk and nfqws*.ipk from there>

View File

@ -0,0 +1,34 @@
#
include $(TOPDIR)/rules.mk
PKG_NAME:=nfqws
PKG_RELEASE:=1
include $(INCLUDE_DIR)/package.mk
define Package/nfqws
SECTION:=net
CATEGORY:=Network
TITLE:=nfqws
SUBMENU:=Zapret
DEPENDS:=+libnetfilter-queue
endef
define Build/Prepare
mkdir -p $(PKG_BUILD_DIR)
$(CP) ./nfq/* $(PKG_BUILD_DIR)/
endef
define Build/Compile
$(MAKE) -C $(PKG_BUILD_DIR) $(TARGET_CONFIGURE_OPTS)
endef
define Package/nfqws/install
$(INSTALL_DIR) $(1)/opt/zapret/nfq
$(INSTALL_BIN) $(PKG_BUILD_DIR)/nfqws $(1)/opt/zapret/nfq
endef
$(eval $(call BuildPackage,nfqws))

View File

@ -0,0 +1 @@
Copy "nfq" folder here !

View File

@ -0,0 +1,32 @@
#
include $(TOPDIR)/rules.mk
PKG_NAME:=tpws
PKG_RELEASE:=1
include $(INCLUDE_DIR)/package.mk
define Package/tpws
SECTION:=net
CATEGORY:=Network
TITLE:=tpws
SUBMENU:=Zapret
endef
define Build/Prepare
mkdir -p $(PKG_BUILD_DIR)
$(CP) ./tpws/* $(PKG_BUILD_DIR)/
endef
define Build/Compile
$(MAKE) -C $(PKG_BUILD_DIR) $(TARGET_CONFIGURE_OPTS)
endef
define Package/tpws/install
$(INSTALL_DIR) $(1)/opt/zapret/tpws
$(INSTALL_BIN) $(PKG_BUILD_DIR)/tpws $(1)/opt/zapret/tpws
endef
$(eval $(call BuildPackage,tpws))

View File

@ -0,0 +1 @@
Copy "tpws" folder here !

95
init.d/debian7/zapret Executable file
View File

@ -0,0 +1,95 @@
#!/bin/sh
# CHOOSE ISP HERE. UNCOMMENT ONLY ONE LINE.
ISP=mns
#ISP=beeline
#ISP=domru
# CHOSE NETWORK INTERFACE BEHIND NAT
SLAVE_ETH=eth0
IPSET_CR=/opt/zapret/ipset/create_ipset.sh
NAME=zapret
DESC=anti-zapret
QNUM=200
TPPORT=1188
ROUTE_TABLE_NUM=100
NFQWS=/opt/zapret/nfq/nfqws
TPWS=/opt/zapret/tpws/tpws
TPWS_USER=tpws
PIDFILE=/var/run/$NAME.pid
set -e
case "$1" in
start)
echo "Creating ipset"
($IPSET_CR)
echo "Adding iptables rule"
case "${ISP}" in
mns)
iptables -t raw -C PREROUTING -p tcp --sport 80 --tcp-flags SYN,ACK SYN,ACK -m set --match-set zapret src -j NFQUEUE --queue-num $QNUM --queue-bypass 2>/dev/null ||
iptables -t raw -I PREROUTING -p tcp --sport 80 --tcp-flags SYN,ACK SYN,ACK -m set --match-set zapret src -j NFQUEUE --queue-num $QNUM --queue-bypass
DAEMON=$NFQWS
DAEMON_OPTS="--qnum=$QNUM --wsize=4"
;;
beeline)
iptables -t mangle -C POSTROUTING -p tcp --dport 80 -m set --match-set zapret dst -j NFQUEUE --queue-num $QNUM --queue-bypass 2>/dev/null ||
iptables -t mangle -I POSTROUTING -p tcp --dport 80 -m set --match-set zapret dst -j NFQUEUE --queue-num $QNUM --queue-bypass
DAEMON=$NFQWS
DAEMON_OPTS="--qnum=$QNUM --hostcase"
;;
domru)
adduser --disabled-login --no-create-home --system --quiet $TPWS_USER
sysctl -w net.ipv4.conf.$SLAVE_ETH.route_localnet=1
iptables -t nat -C PREROUTING -p tcp --dport 80 -i $SLAVE_ETH -m set --match-set zapret dst -j DNAT --to 127.0.0.1:$TPPORT 2>/dev/null ||
iptables -t nat -I PREROUTING -p tcp --dport 80 -i $SLAVE_ETH -m set --match-set zapret dst -j DNAT --to 127.0.0.1:$TPPORT
iptables -t nat -C OUTPUT -p tcp --dport 80 -m owner ! --uid-owner $TPWS_USER -m set --match-set zapret dst -j DNAT --to 127.0.0.1:$TPPORT 2>/dev/null ||
iptables -t nat -I OUTPUT -p tcp --dport 80 -m owner ! --uid-owner $TPWS_USER -m set --match-set zapret dst -j DNAT --to 127.0.0.1:$TPPORT
DAEMON=$TPWS
DAEMON_OPTS="--port=$TPPORT --hostcase --split-http-req=host --user=$TPWS_USER --bind-addr=127.0.0.1"
;;
esac
echo -n "Starting $DESC: "
start-stop-daemon --start --quiet --pidfile $PIDFILE --background --make-pidfile \
--exec $DAEMON -- $DAEMON_OPTS
echo "$NAME."
;;
stop)
echo "Deleting iptables rule"
case "${ISP}" in
mns)
iptables -t raw -D PREROUTING -p tcp --sport 80 --tcp-flags SYN,ACK SYN,ACK -m set --match-set zapret src -j NFQUEUE --queue-num $QNUM --queue-bypass
DAEMON=$NFQWS
;;
beeline)
iptables -t mangle -D POSTROUTING -p tcp --dport 80 -m set --match-set zapret dst -j NFQUEUE --queue-num $QNUM --queue-bypass
DAEMON=$NFQWS
;;
domru)
sysctl -w net.ipv4.conf.$SLAVE_ETH.route_localnet=0
iptables -t nat -D PREROUTING -p tcp --dport 80 -i $SLAVE_ETH -m set --match-set zapret dst -j DNAT --to 127.0.0.1:$TPPORT
iptables -t nat -D OUTPUT -p tcp --dport 80 -m owner ! --uid-owner $TPWS_USER -m set --match-set zapret dst -j DNAT --to 127.0.0.1:$TPPORT
DAEMON=$TPWS
;;
esac
echo -n "Stopping $DESC: "
start-stop-daemon --oknodo --stop --quiet --pidfile $PIDFILE \
--exec $DAEMON
echo "$NAME."
;;
*)
N=/etc/init.d/$NAME
echo "Usage: $N {start|stop}" >&2
exit 1
;;
esac
exit 0

35
init.d/openwrt/99-zapret Normal file
View File

@ -0,0 +1,35 @@
# copy it to /etc/hotplug.d/firewall/99-zapret
# CHOOSE ISP HERE. UNCOMMENT ONLY ONE LINE.
# if your ISP not in list then comment all lines
ISP=domru
TPPORT=1188
TPWS_USER=daemon
case "$ACTION" in
add)
case "$ISP" in
domru)
case "$INTERFACE" in
wan)
# BLOCK SPOOFED DNS FROM DOMRU
iptables -t raw -C PREROUTING -i $DEVICE -p udp --sport 53 -m string --hex-string "|5cfff164|" --algo bm -j DROP --from 40 --to 300 ||
iptables -t raw -I PREROUTING -i $DEVICE -p udp --sport 53 -m string --hex-string "|5cfff164|" --algo bm -j DROP --from 40 --to 300
iptables -t raw -C PREROUTING -i $DEVICE -p udp --sport 53 -m string --hex-string "|2a022698a00000000000000000000064|" --algo bm -j DROP --from 40 --to 300 ||
iptables -t raw -I PREROUTING -i $DEVICE -p udp --sport 53 -m string --hex-string "|2a022698a00000000000000000000064|" --algo bm -j DROP --from 40 --to 300
# DNAT for local traffic
iptables -t nat -C OUTPUT -p tcp --dport 80 -o $DEVICE -m owner ! --uid-owner $TPWS_USER -m set --match-set zapret dst -j DNAT --to 127.0.0.1:$TPPORT ||
iptables -t nat -I OUTPUT -p tcp --dport 80 -o $DEVICE -m owner ! --uid-owner $TPWS_USER -m set --match-set zapret dst -j DNAT --to 127.0.0.1:$TPPORT
;;
lan)
# DNAT for pass-thru traffic
sysctl -w net.ipv4.conf.$DEVICE.route_localnet=1
iptables -t nat -C prerouting_lan_rule -p tcp --dport 80 -m set --match-set zapret dst -j DNAT --to 127.0.0.1:$TPPORT ||
iptables -t nat -I prerouting_lan_rule -p tcp --dport 80 -m set --match-set zapret dst -j DNAT --to 127.0.0.1:$TPPORT
;;
esac
;;
esac
esac

View File

@ -0,0 +1,5 @@
# put it to /etc/firewall.user
# for BEELINE ISP
iptables -t mangle -D POSTROUTING -p tcp --dport 80 -m set --match-set zapret dst -j NFQUEUE --queue-num 200 --queue-bypass 2>/dev/null
iptables -t mangle -I POSTROUTING -p tcp --dport 80 -m set --match-set zapret dst -j NFQUEUE --queue-num 200 --queue-bypass

View File

@ -0,0 +1,5 @@
# put it to /etc/firewall.user
# for MNS ISP
iptables -t raw -D PREROUTING -p tcp --sport 80 --tcp-flags SYN,ACK SYN,ACK -m set --match-set zapret src -j NFQUEUE --queue-num 200 --queue-bypass 2>/dev/null
iptables -t raw -I PREROUTING -p tcp --sport 80 --tcp-flags SYN,ACK SYN,ACK -m set --match-set zapret src -j NFQUEUE --queue-num 200 --queue-bypass

53
init.d/openwrt/zapret Executable file
View File

@ -0,0 +1,53 @@
#!/bin/sh /etc/rc.common
# Copyright (C) 2006-2011 OpenWrt.org
# CHOOSE ISP HERE. UNCOMMENT ONLY ONE LINE.
#ISP=mns
#ISP=beeline
ISP=domru
# !!!!! in openwrt you need to add firewall rules manually to /etc/firewall.user or /etc/hotplug.d/firewall/99-zapret
QNUM=200
TPPORT=1188
ROUTE_TABLE_NUM=100
NFQWS=/opt/zapret/nfq/nfqws
TPWS=/opt/zapret/tpws/tpws
IPSET_CR=/opt/zapret/ipset/create_ipset.sh
TPWS_USER=daemon
# start betfore firewall - we need ipset populated
START=18
get_daemon() {
case "${ISP}" in
mns)
DAEMON_OPTS="--qnum=$QNUM --wsize=4"
DAEMON=$NFQWS
;;
beeline)
DAEMON_OPTS="--qnum=$QNUM --hostcase"
DAEMON=$NFQWS
;;
domru)
DAEMON_OPTS="--port=$TPPORT --hostcase --split-http-req=host --bind-addr=127.0.0.1 --user=$TPWS_USER"
DAEMON=$TPWS
;;
esac
}
start() {
echo "Creating ipset"
($IPSET_CR)
get_daemon
echo "Starting $DAEMON"
service_start $DAEMON --daemon $DAEMON_OPTS
}
stop() {
get_daemon
service_stop $DAEMON
}

View File

@ -0,0 +1,77 @@
description "boinc"
start on runlevel [2345]
stop on runlevel [!2345]
# CHOOSE ISP HERE. UNCOMMENT ONLY ONE LINE.
env ISP=mns
#env ISP=beeline
#env ISP=domru
# CHOSE NETWORK INTERFACE BEHIND NAT
env SLAVE_ETH=eth1
env QNUM=200
env TPPORT=1188
env ROUTE_TABLE_NUM=100
env NFQWS=/opt/zapret/nfq/nfqws
env TPWS=/opt/zapret/tpws/tpws
env TPWS_USER=tpws
pre-start script
/opt/zapret/ipset/create_ipset.sh
case "${ISP}" in
mns)
iptables -t raw -C PREROUTING -p tcp --sport 80 --tcp-flags SYN,ACK SYN,ACK -m set --match-set zapret src -j NFQUEUE --queue-num $QNUM --queue-bypass ||
iptables -t raw -I PREROUTING -p tcp --sport 80 --tcp-flags SYN,ACK SYN,ACK -m set --match-set zapret src -j NFQUEUE --queue-num $QNUM --queue-bypass
;;
beeline)
iptables -t mangle -C POSTROUTING -p tcp --dport 80 -m set --match-set zapret dst -j NFQUEUE --queue-num $QNUM --queue-bypass ||
iptables -t mangle -I POSTROUTING -p tcp --dport 80 -m set --match-set zapret dst -j NFQUEUE --queue-num $QNUM --queue-bypass
;;
domru)
adduser --disabled-login --no-create-home --system --quiet $TPWS_USER
sysctl -w net.ipv4.conf.$SLAVE_ETH.route_localnet=1
iptables -t nat -C PREROUTING -p tcp --dport 80 -i $SLAVE_ETH -m set --match-set zapret dst -j DNAT --to 127.0.0.1:$TPPORT ||
iptables -t nat -I PREROUTING -p tcp --dport 80 -i $SLAVE_ETH -m set --match-set zapret dst -j DNAT --to 127.0.0.1:$TPPORT
iptables -t nat -C OUTPUT -p tcp --dport 80 -m owner ! --uid-owner $TPWS_USER -m set --match-set zapret dst -j DNAT --to 127.0.0.1:$TPPORT ||
iptables -t nat -I OUTPUT -p tcp --dport 80 -m owner ! --uid-owner $TPWS_USER -m set --match-set zapret dst -j DNAT --to 127.0.0.1:$TPPORT
;;
esac
end script
script
case "${ISP}" in
mns)
NFEXE=$NFQWS
NFARG="--qnum $QNUM --wsize=4"
;;
beeline)
NFEXE=$NFQWS
NFARG="--qnum $QNUM --hostcase"
;;
domru)
NFEXE=$TPWS
NFARG="--port=$TPPORT --hostcase --split-http-req=host --user=$TPWS_USER --bind-addr=127.0.0.1"
;;
esac
$NFEXE $NFARG
end script
pre-stop script
case "${ISP}" in
mns)
iptables -t raw -D PREROUTING -p tcp --sport 80 --tcp-flags SYN,ACK SYN,ACK -m set --match-set zapret src -j NFQUEUE --queue-num $QNUM --queue-bypass
;;
beeline)
iptables -t mangle -D POSTROUTING -p tcp --dport 80 -m set --match-set zapret dst -j NFQUEUE --queue-num $QNUM --queue-bypass
;;
domru)
sysctl -w net.ipv4.conf.$SLAVE_ETH.route_localnet=0
iptables -t nat -D PREROUTING -p tcp --dport 80 -i $SLAVE_ETH -m set --match-set zapret dst -j DNAT --to 127.0.0.1:$TPPORT
iptables -t nat -D OUTPUT -p tcp --dport 80 -m owner ! --uid-owner $TPWS_USER -m set --match-set zapret dst -j DNAT --to 127.0.0.1:$TPPORT
;;
esac
end script

25
ipset/create_ipset.sh Executable file
View File

@ -0,0 +1,25 @@
#!/bin/sh
# create ipset from resolved ip's
SCRIPT=$(readlink -f $0)
EXEDIR=$(dirname $SCRIPT)
. "$EXEDIR/def.sh"
TEMPIPSET=/tmp/ipset.$ZIPSET.tmp
ipset flush $ZIPSET || ipset create $ZIPSET hash:ip
for f in "$ZIPLIST" "$ZIPLIST_USER"
do
[ -f $TEMPIPSET ] && rm -f $TEMPIPSET
[ -n "$f" ] && {
echo Adding $f
sort $f | uniq | while read ip;
do
echo add $ZIPSET $ip >>$TEMPIPSET
done
ipset -! restore <$TEMPIPSET 2>&1
rm -f $TEMPIPSET
}
done

4
ipset/def.sh Executable file
View File

@ -0,0 +1,4 @@
ZIPLIST=$EXEDIR/zapret-ip.txt
ZIPLIST_USER=$EXEDIR/zapret-ip-user.txt
ZIPSET=zapret
ZUSERLIST=$EXEDIR/zapret-hosts-user.txt

34
ipset/get_reestr.sh Executable file
View File

@ -0,0 +1,34 @@
#!/bin/sh
# get rublacklist and resolve it
SCRIPT=$(readlink -f $0)
EXEDIR=$(dirname $SCRIPT)
. "$EXEDIR/def.sh"
ZREESTR=/tmp/zapret.txt
ZDIG=/tmp/zapret-dig.txt
ZIPLISTTMP=/tmp/zapret-ip.txt
ZURL=http://reestr.rublacklist.net/api/current
$EXEDIR/get_user.sh
curl --fail --max-time 60 --max-filesize 10485760 "$ZURL" >$ZREESTR && {
dlsize=$(wc -c "$ZREESTR" | cut -f 1 -d ' ')
if test $dlsize -lt 204800; then
echo list file is too small. can be bad.
exit 2
fi
sed -i 's/\\n/\r\n/g' $ZREESTR
sed -nre 's/^[^;]*;([^;|\\]{4,250})\;.*/\1/p' $ZREESTR | sort | uniq >$ZDIG
rm -f $ZREESTR
echo digging started ...
dig A +short +time=8 +tries=2 -f $ZDIG | grep -E '^[^;].*[^.]$' | grep -vE '^192.168.[0-9]*.[0-9]*$' | grep -vE '^127.[0-9]*.[0-9]*.[0-9]*$' | grep -vE '^10.[0-9]*.[0-9]*.[0-9]*$' >$ZIPLISTTMP || {
rm -f $ZDIG
exit 1
}
rm -f $ZDIG $ZIPLIST
sort $ZIPLISTTMP | uniq >$ZIPLIST
rm -f $ZIPLISTTMP
"$EXEDIR/create_ipset.sh"
}

11
ipset/get_user.sh Executable file
View File

@ -0,0 +1,11 @@
#!/bin/sh
# resolve user host list
SCRIPT=$(readlink -f $0)
EXEDIR=$(dirname $SCRIPT)
. "$EXEDIR/def.sh"
[ -f $ZUSERLIST ] && {
dig A +short +time=8 +tries=2 -f $ZUSERLIST | grep -E '^[^;].*[^.]$' | grep -vE '^192.168.[0-9]*.[0-9]*$' | grep -vE '^127.[0-9]*.[0-9]*.[0-9]*$' | grep -vE '^10.[0-9]*.[0-9]*.[0-9]*$' | sort | uniq >$ZIPLIST_USER
}

View File

@ -0,0 +1,5 @@
st.kinozal.tv
s.kinozal.tv
static.rutracker.org
static2.rutracker.org
bt.rutracker.org

5
ipset/zapret-ip-user.txt Normal file
View File

@ -0,0 +1,5 @@
195.60.77.222
195.82.146.120
195.82.146.216
195.82.146.52
93.114.40.103

7397
ipset/zapret-ip.txt Normal file

File diff suppressed because it is too large Load Diff

33
iptables.txt Normal file
View File

@ -0,0 +1,33 @@
For window size changing :
iptables -t raw -I PREROUTING -p tcp --sport 80 --tcp-flags SYN,ACK SYN,ACK -j NFQUEUE --queue-num 200 --queue-bypass
iptables -t raw -I PREROUTING -p tcp --sport 80 --tcp-flags SYN,ACK SYN,ACK -m set --match-set zapret src -j NFQUEUE --queue-num 200 --queue-bypass
For outgoing data manipulation ("Host:" case changing) :
iptables -t mangle -I POSTROUTING -p tcp --dport 80 -j NFQUEUE --queue-num 200 --queue-bypass
iptables -t mangle -I POSTROUTING -p tcp --dport 80 -m set --match-set zapret dst -j NFQUEUE --queue-num 200 --queue-bypass
iptables -t mangle -I POSTROUTING -p tcp --dport 80 -m set --match-set zapret dst -m connbytes --connbytes-dir=original --connbytes-mode=packets --connbytes 1:5 -j NFQUEUE --queue-num 200 --queue-bypass
For TPROXY :
sysctl -w net.ipv4.ip_forward=1
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
ip -f inet rule add fwmark 1 lookup 100
ip -f inet route add local default dev lo table 100
# prevent loop
iptables -t filter -I INPUT -p tcp --dport 1188 -j REJECT
iptables -t mangle -A PREROUTING -i eth1 -p tcp --dport 80 -j MARK --set-mark 1
iptables -t mangle -A PREROUTING -i eth1 -p tcp --dport 80 -j TPROXY --tproxy-mark 0x1/0x1 --on-port 1188
iptables -t mangle -A PREROUTING -i eth1 -p tcp --dport 80 -m set --match-set zapret dst -j MARK --set-mark 1
iptables -t mangle -A PREROUTING -i eth1 -p tcp --dport 80 -m mark --mark 0x1/0x1 -j TPROXY --tproxy-mark 0x1/0x1 --on-port 1188
For DNAT :
# run tpws as user "tpws". its required to avoid loops.
sysctl -w net.ipv4.conf.eth1.route_localnet=1
iptables -t nat -I PREROUTING -p tcp --dport 80 -j DNAT --to 127.0.0.1:1188
iptables -t nat -I OUTPUT -p tcp --dport 80 -m owner ! --uid-owner tpws -j DNAT --to 127.0.0.1:1188

13
nfq/Makefile Normal file
View File

@ -0,0 +1,13 @@
CFLAGS=-Wall
LIBS=-lnetfilter_queue -lnfnetlink
all: nfqws
nfqws: nfqws.o
$(CC) $(CFLAGS) -o "$@" $< $(LIBS)
nfqws.o: nfqws.c
$(CC) $(CFLAGS) -c $<
clean:
rm -f *.o nfqws

374
nfq/nfqws.c Normal file
View File

@ -0,0 +1,374 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdbool.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <netinet/in.h>
#include <linux/types.h>
#include <linux/netfilter.h> /* for NF_ACCEPT */
#include <libnetfilter_queue/libnetfilter_queue.h>
#include <getopt.h>
#include <fcntl.h>
#include <pwd.h>
bool proto_check_ipv4(unsigned char *data,int len)
{
return len && (data[0] & 0xF0)==0x40 &&
len>=((data[0] & 0x0F)<<2);
}
void proto_skip_ipv4(unsigned char **data,int *len)
{
int l;
l = (**data & 0x0F)<<2;
*data += l;
*len -= l;
}
bool proto_check_tcp(unsigned char *data,int len)
{
return len>=((data[12] & 0xF0)>>2);
}
void proto_skip_tcp(unsigned char **data,int *len)
{
int l;
l = ((*data)[12] & 0xF0)>>2;
*data += l;
*len -= l;
}
unsigned char *find_bin(unsigned char *data,int len,const void *blk,int blk_len)
{
while (len>=blk_len)
{
if (!memcmp(data,blk,blk_len))
return data;
data++;
len--;
}
return NULL;
}
static inline bool tcp_synack_segment( const struct tcphdr *tcphdr )
{
/* check for set bits in TCP hdr */
return tcphdr->urg == 0 &&
tcphdr->ack == 1 &&
tcphdr->psh == 0 &&
tcphdr->rst == 0 &&
tcphdr->syn == 1 &&
tcphdr->fin == 0;
}
uint16_t checksum(const void *buff, int len, in_addr_t src_addr, in_addr_t dest_addr)
{
const uint16_t *buf=buff;
uint16_t *ip_src=(void *)&src_addr, *ip_dst=(void *)&dest_addr;
uint32_t sum;
int length=len;
// Calculate the sum
sum = 0;
while (len > 1)
{
sum += *buf++;
if (sum & 0x80000000)
sum = (sum & 0xFFFF) + (sum >> 16);
len -= 2;
}
if ( len & 1 )
// Add the padding if the packet lenght is odd
sum += *((uint8_t *)buf);
// Add the pseudo-header
sum += *(ip_src++);
sum += *ip_src;
sum += *(ip_dst++);
sum += *ip_dst;
sum += htons(IPPROTO_TCP);
sum += htons(length);
// Add the carries
while (sum >> 16)
sum = (sum & 0xFFFF) + (sum >> 16);
// Return the one's complement of sum
return (uint16_t)(~sum);
}
void tcp_fix_checksum(struct tcphdr *tcp,int len, in_addr_t src_addr, in_addr_t dest_addr)
{
tcp->check = 0;
tcp->check = checksum(tcp,len,src_addr,dest_addr);
}
void tcp_rewrite_winsize(struct tcphdr *tcp,uint16_t winsize)
{
unsigned int winsize_old;
/*
unsigned char scale_factor=1;
int optlen = (tcp->doff << 2);
unsigned char *opt = (unsigned char*)(tcp+1);
optlen = optlen>sizeof(struct tcphdr) ? optlen-sizeof(struct tcphdr) : 0;
printf("optslen=%d\n",optlen);
while (optlen)
{
switch(*opt)
{
case 0: break; // end of option list;
case 1: opt++; optlen--; break; // noop
default:
if (optlen<2 || optlen<opt[1]) break;
if (*opt==3 && opt[1]>=3)
{
scale_factor=opt[2];
printf("Found scale factor %u\n",opt[2]);
//opt[2]=0;
}
optlen-=opt[1];
opt+=opt[1];
}
}
*/
winsize_old = htons(tcp->window); // << scale_factor;
tcp->window = htons(winsize);
printf("Window size change %u => %u\n",winsize_old,winsize);
}
struct cbdata_s
{
int wsize;
int qnum;
bool hostcase;
};
// ret: false - not modified, true - modified
bool processPacketData(unsigned char *data,int len,const struct cbdata_s *cbdata)
{
struct iphdr *iphdr = NULL;
struct tcphdr *tcphdr = NULL;
unsigned char *p;
int len_tcp;
bool bRet = false;
if (proto_check_ipv4(data,len))
{
iphdr = (struct iphdr *) data;
proto_skip_ipv4(&data,&len);
if (iphdr->protocol==6 && proto_check_tcp(data,len))
{
tcphdr = (struct tcphdr *) data;
len_tcp = len;
proto_skip_tcp(&data,&len);
//printf("got TCP packet. payload_len=%d\n",len);
if (cbdata->wsize && tcp_synack_segment(tcphdr))
{
tcp_rewrite_winsize(tcphdr,(uint16_t)cbdata->wsize);
bRet = true;
}
if (cbdata->hostcase && (p = find_bin(data,len,"\r\nHost: ",8)))
{
printf("modifying Host: => host:\n");
p[2]='h'; // "Host:" => "host:"
bRet = true;
}
if (bRet) tcp_fix_checksum(tcphdr,len_tcp,iphdr->saddr,iphdr->daddr);
}
}
return bRet;
}
static int cb(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg,
struct nfq_data *nfa, void *cookie)
{
int id,len;
struct nfqnl_msg_packet_hdr *ph;
unsigned char *data;
const struct cbdata_s *cbdata = (struct cbdata_s*)cookie;
ph = nfq_get_msg_packet_hdr(nfa);
id = ph ? ntohl(ph->packet_id) : 0;
len = nfq_get_payload(nfa, &data);
printf("packet: id=%d len=%d\n",id,len);
if (len >= 0)
{
if (processPacketData(data, len, cbdata))
return nfq_set_verdict(qh, id, NF_ACCEPT, len, data);
}
return nfq_set_verdict(qh, id, NF_ACCEPT, 0, NULL);
}
bool droproot(uid_t uid, gid_t gid)
{
if (uid)
{
if (setgid(gid))
{
perror("setgid: ");
return false;
}
if (setuid(uid))
{
perror("setuid: ");
return false;
}
}
return true;
}
void exithelp()
{
printf(" --qnum=<nfqueue_number>\n --wsize=<window_size>\t; set window size. 0 = do not modify\n --hostcase\t\t; change Host: => host:\n --daemon\t\t; daemonize\n");
exit(1);
}
int main(int argc, char **argv)
{
struct nfq_handle *h;
struct nfq_q_handle *qh;
int fd;
int rv;
char buf[4096] __attribute__ ((aligned));
struct cbdata_s cbdata;
int option_index=0;
int v;
bool daemon=false;
uid_t uid=0;
gid_t gid;
memset(&cbdata,0,sizeof(cbdata));
const struct option long_options[] = {
{"qnum",required_argument,0,0}, // optidx=0
{"daemon",no_argument,0,0}, // optidx=1
{"wsize",required_argument,0,0}, // optidx=2
{"hostcase",no_argument,0,0}, // optidx=3
{"user",required_argument,0,0}, // optidx=4
{NULL,0,NULL,0}
};
if (argc<2) exithelp();
while ((v=getopt_long_only(argc,argv,"",long_options,&option_index))!=-1)
{
if (v) exithelp();
switch(option_index)
{
case 0: /* qnum */
cbdata.qnum=atoi(optarg);
if (cbdata.qnum<0 || cbdata.qnum>65535)
{
fprintf(stdout,"bad qnum\n");
exit(1);
}
break;
case 1: /* daemon */
daemon = true;
break;
case 2: /* wsize */
cbdata.wsize=atoi(optarg);
if (cbdata.wsize<0 || cbdata.wsize>65535)
{
fprintf(stdout,"bad qnum\n");
exit(1);
}
break;
case 3: /* hostcase */
cbdata.hostcase = true;
break;
case 4: /* user */
{
struct passwd *pwd = getpwnam(optarg);
if (!pwd)
{
fprintf(stderr,"non-existent username supplied\n");
exit(1);
}
uid = pwd->pw_uid;
gid = pwd->pw_gid;
break;
}
}
}
if (daemon)
{
int pid;
pid = fork();
if (pid == -1)
return -1;
else if (pid != 0)
return 0;
if (setsid() == -1)
return -1;
if (chdir ("/") == -1)
return -1;
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
/* redirect fd's 0,1,2 to /dev/null */
open ("/dev/null", O_RDWR);
/* stdin */
dup(0);
/* stdout */
dup(0);
/* stderror */
}
printf("opening library handle\n");
h = nfq_open();
if (!h) {
fprintf(stderr, "error during nfq_open()\n");
exit(1);
}
printf("unbinding existing nf_queue handler for AF_INET (if any)\n");
if (nfq_unbind_pf(h, AF_INET) < 0) {
fprintf(stderr, "error during nfq_unbind_pf()\n");
exit(1);
}
printf("binding nfnetlink_queue as nf_queue handler for AF_INET\n");
if (nfq_bind_pf(h, AF_INET) < 0) {
fprintf(stderr, "error during nfq_bind_pf()\n");
exit(1);
}
printf("binding this socket to queue '%u'\n", cbdata.qnum);
qh = nfq_create_queue(h, cbdata.qnum, &cb, &cbdata);
if (!qh) {
fprintf(stderr, "error during nfq_create_queue()\n");
exit(1);
}
printf("setting copy_packet mode\n");
if (nfq_set_mode(qh, NFQNL_COPY_PACKET, 0xffff) < 0) {
fprintf(stderr, "can't set packet_copy mode\n");
exit(1);
}
fd = nfq_fd(h);
if (droproot(uid,gid))
{
while ((rv = recv(fd, buf, sizeof(buf), 0)) && rv >= 0)
{
int r=nfq_handle_packet(h, buf, rv);
if (r) fprintf(stderr,"nfq_handle_packet error %d\n",r);
}
}
printf("unbinding from queue 0\n");
nfq_destroy_queue(qh);
#ifdef INSANE
/* normally, applications SHOULD NOT issue this command, since
* it detaches other programs/sockets from AF_INET, too ! */
printf("unbinding from AF_INET\n");
nfq_unbind_pf(h, AF_INET);
#endif
printf("closing library handle\n");
nfq_close(h);
exit(0);
}

BIN
nfq/nfqws.o Normal file

Binary file not shown.

169
readme.txt Normal file
View File

@ -0,0 +1,169 @@
zapret v.3
Для чего это надо
-----------------
Обойти блокировки веб сайтов http.
Как это работает
----------------
У провайдеров в DPI бывают бреши. Они случаются от того, что правила DPI пишут для
обычных пользовательских программ, опуская все возможные случаи, допустимые по стандартам.
Это делается для простоты и скорости. Нет смысла ловить хакеров, которых 0.01%,
ведь все равно эти блокировки обходятся довольно просто даже обычными пользователями.
Некоторые DPI не могут распознать http запрос, если он разделен на TCP сегменты.
Например, запрос вида "GET / HTTP/1.1\r\nHost: kinozal.tv......"
мы посылаем 2 частями : сначала идет "GET ", затем "/ HTTP/1.1\r\nHost: kinozal.tv.....".
Как заставить систему разбивать запрос на 2 части ? Подменить поле tcp window size
на первом входящем TCP пакете с SYN,ACK. Тогда клиент подумает, что сервер установил
для него маленький window size и первый сегмент с данными отошлет не более указанной длины.
В следующем пакете мы не будем менять ничего, поэтому клиент это поймет так,
что сервер увеличил window size, и все пойдет как обычно.
Другие DPI спотыкаются, когда заголовок "Host:" пишется в другом регистре : например, "host:".
Как это реализовать на практике в системе linux
-----------------------------------------------
Перехватить пакет с SYN,ACK не представляет никакой сложности средствами iptables.
Однако, возможности редактирования пакетов в iptables сильно ограничены.
Просто так поменять window size стандартными модулями нельзя.
Для этого мы воспользуемся средством NFQUEUE. Это средство позволяет
передавать пакеты на обработку процессам, работающим в user mode.
Процесс, приняв пакет, может его изменить, что нам и нужно.
iptables -t raw -I PREROUTING -p tcp --sport 80 --tcp-flags SYN,ACK SYN,ACK -j NFQUEUE --queue-num 200 --queue-bypass
Будет отдавать нужные нам пакеты процессу, слушающему на очереди с номером 200.
Он подменит window size. PREROUTING поймает как пакеты, адресованные самому хосту,
так и маршрутизируемые пакеты. То есть решение одинаково работает как на клиенте,
так и на роутере. На роутере на базе PC или на базе OpenWRT.
В принципе этого достаточно.
Однако, при таком воздействии на TCP будет небольшая задержка при установлении соединения.
От 0.5 до 1.5 сек.
Чтобы не трогать хосты, которые не блокируются провайдером, можно сделать такой ход.
Создать список заблоченых доменов или скачать его с rublacklist.
Заресолвить все домены в ipv4 адреса. Загнать их в ipset с именем "zapret".
Добавить в правило :
iptables -t raw -I PREROUTING -p tcp --sport 80 --tcp-flags SYN,ACK SYN,ACK -m set --match-set zapret src -j NFQUEUE --queue-num 200 --queue-bypass
Такии образом воздействие будет производиться только на ip адреса, относящиеся к заблокированным сайтам.
Список можно обновлять через cron раз в несколько дней.
Если обновлять через rublacklist, то это займет довольно долго. Более часа. Но ресурсов
этот процесс не отнимает, так что никаких проблем это не вызовет, особенно, если система
работает постоянно.
Если DPI не обходится через разделение запроса на сегменты, то иногда срабатывает изменение
"Host:" на "host:". В этом случае нам может не понадобится замена window size, поэтому цепочка
PREROUTING нам не нужна. Вместо нее вешаемся на исходящие пакеты в цепочке POSTROUTING :
iptables -t mangle -I POSTROUTING -p tcp --dport 80 -m set --match-set zapret dst -j NFQUEUE --queue-num 200 --queue-bypass
В этом случае так же возможны дополнительные моменты. DPI может ловить только первый http запрос, игнорируя
последующие запросы в keep-alive сессии. Тогда можем уменьшить нагрузку на проц, отказавшись от процессинга ненужных пакетов.
iptables -t mangle -I POSTROUTING -p tcp --dport 80 -m set --match-set zapret dst -m connbytes --connbytes-dir=original --connbytes-mode=packets --connbytes 1:5 -j NFQUEUE --queue-num 200 --queue-bypass
Случается так, что провайдер мониторит всю HTTP сессию с keep-alive запросами. В этом случае
недостаточно ограничивать TCP window при установлении соединения. Необходимо посылать отдельными
TCP сегментами каждый новый запрос. Эта задача решается через полное проксирование трафика через
transparent proxy (TPROXY или DNAT). TPROXY не работает с соединениями, исходящими с локальной системы,
так что это решение применимо только на роутере. DNAT работает и с локальными соединениеми,
но имеется опасность входа в бесконечную рекурсию, поэтому демон запускается под отдельным пользователем,
и для этого пользователя отключается DNAT через "-m owner". Полное проксирование требует больше ресурсов
процессора, чем манипуляция с исходящими пакетами без реконструкции TCP соединения.
nfqws
-----
Эта программа и есть модификатор пакетов и обработчик очереди NFQUEUE.
Она берет следующие параметры :
--qnum=200 ; номер очереди
--wsize=4 ; менять tcp window size на указанный размер
--hostcase ; менять регистр заголовка "Host:"
--daemon ; демонизировать прогу
tpws
-----
tpws - это transparent proxy.
--bind-addr ; на каком адресе слушать
--port=<port> ; на каком порту слушать
--split-http-req=method|host ; способ разделения http запросов на сегменты : около метода (GET,POST) или около заголовка Host
--hostcase ; change Host: => host:
--daemon ; daemonize
Провайдеры
----------
mns.ru : нужна замена window size на 4
beeline (corbina) : нужна замена регистра "Host:" на протяжении всей http сессии
dom.ru : нужно проксирование HTTP сессий через tpws с заменой регистра "Host:" и разделение TCP сегментов на хедере "Host:".
Ахтунг ! Домру блокирует все поддомены заблоченого домена. IP адреса всевозможных поддоменов узнать невозможно из реестра
блокировок, поэтому если вдруг на каком-то сайте вылезает блокировочный баннер, то идите в консоль firefox, вкладка network.
Загружайте сайт и смотрите куда идет редирект. Потом вносите домен в zapret-hosts-user.txt. Например, на kinozal.tv имеются
2 запрашиваемых поддомена : s.kinozal.tv и st.kinozal.tv с разными IP адресами.
Пример установки на debian 7
----------------------------
Установить пакеты :
apt-get update
apt-get install libnetfilter-queue-dev ipset curl
Скопировать директорию "zapret" в /opt.
Собрать nfqws :
cd /opt/zapret/nfq
make
Собрать tpws :
cd /opt/zapret/tpws
make
Скопировать /opt/zapret/init.d/debian7/zapret в /etc/init.d.
В /etc/init.d/zapret выбрать пераметр "ISP". В зависимости от него будут применены нужные правила.
Там же выбрать параметр SLAVE_ETH, соответствующий названию внутреннего сетевого интерфейса.
Включить автостарт : chkconfig zapret on
(опционально) Вручную первый раз получить новый список ip адресов : /opt/zapret/ipset/get_reestr.sh
Зашедулить задание обновления листа :
crontab -e
Создать строчку "0 12 * * */2 /opt/zapret/ipset/get_reestr.sh". Это значит в 12:00 каждые 2 дня обновлять список.
Запустить службу : service zapret start
Попробовать зайти куда-нибудь : http://ej.ru, http://kinozal.tv, http://grani.ru.
Если не работает, то остановить службу zapret, добавить правило в iptables вручную,
запустить nfqws в терминале под рутом с нужными параметрами.
Пытаться подключаться к заблоченым сайтам, смотреть вывод программы.
Если нет никакой реакции, значит скорее всего указан неверный номер очереди или ip назначения нет в ipset.
Если реакция есть, но блокировка не обходится, значит параметры обхода подобраные неверно, или это средство
не работает в вашем случае на вашем провайдере.
Никто и не говорил, что это будет работать везде.
Попробуйте снять дамп в wireshark или "tcpdump -vvv -X host <ip>", посмотрите действительно ли первый
сегмент TCP уходит коротким и меняется ли регистр "Host:".
Что делать с openwrt
--------------------
Установить дополнительные пакеты :
opkg update
opkg install iptables-mod-extra iptables-mod-nfqueue iptables-mod-filter iptables-mod-ipopt ipset curl bind-tools
Самая главная трудность - скомпилировать программы на C.
Это можно сделать средствами кросс-компиляции на любой традиционной linux системе.
Читайте compile/build_howto_openwrt.txt.
Ваша задача - получить ipk файлы для tpws и nfqws.
Скопировать директорию "zapret" в /opt на роутер.
Установить ipk. Для этого сначала копируем на роутер ipk в /tmp, потом opkg install /tmp/*.ipk.
Смотрим, что появились исполняемые файлы /opt/zapret/tpws/tpws, /opt/zapret/nfq/nfqws.
Скопировать /opt/zapret/init.d/zapret в /etc/init.d.
В /etc/init.d/zapret выбрать пераметр "ISP". В зависимости от него будут применены нужные правила.
/etc/init.d/zapret enable
/etc/init.d/zapret start
В зависимости от вашего провайдера либо внести нужные записи в /etc/firewall.user, либо
скопировать 99-zapret в /etc/hotplug.d/firewall (сначала нужно mkdir /etc/hotplug.d/firewall).
В /etc/hotplug.d/firewall/99-zapret выбрать нужного провайдера.
/etc/init.d/firewall restart
Посмотреть через iptables -L или через luci вкладку "firewall" появились ли нужные правила.
Зашедулить задание обновления листа :
crontab -e
Создать строчку "0 12 * * */2 /opt/zapret/ipset/get_reestr.sh". Это значит в 12:00 каждые 2 дня обновлять список.

14
tpws/Makefile Normal file
View File

@ -0,0 +1,14 @@
CC = gcc
CFLAGS =
LIBS =
SRC_FILES = *.c
all: tpws
tpws: $(SRC_FILES)
$(CC) $(CFLAGS) -o $@ $^ $(LIBS)
clean:
rm -f tpws *.o
.PHONY: clean

597
tpws/tpws.c Normal file
View File

@ -0,0 +1,597 @@
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/ip.h>
#include <netdb.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/select.h>
#include <fcntl.h>
#include <stdint.h>
#include <sys/queue.h>
#include <sys/epoll.h>
#include <sys/ioctl.h>
#include <signal.h>
#include <errno.h>
#include <stdbool.h>
#include <netinet/tcp.h>
#include <getopt.h>
#include <pwd.h>
#include "tpws.h"
#include "tpws_conn.h"
enum splithttpreq {split_none=0,split_method,split_host};
struct params_s
{
char bindaddr[64];
uid_t uid;
gid_t gid;
uint16_t port;
bool daemon;
bool hostcase,methodcase;
enum splithttpreq split_http_req;
int maxconn;
};
struct params_s params;
unsigned char *find_bin(void *data,ssize_t len,const void *blk,ssize_t blk_len)
{
while (len>=blk_len)
{
if (!memcmp(data,blk,blk_len))
return data;
data=(char*)data+1;
len--;
}
return NULL;
}
ssize_t send_with_flush(int sockfd, const void *buf, size_t len, int flags)
{
int flag,err;
ssize_t wr;
flag=1;
setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, (char *) &flag, sizeof(int));
wr=send(sockfd,buf,len,flags);
err=errno;
flag=0;
setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, (char *) &flag, sizeof(int));
errno=err;
return wr;
}
void close_tcp_conn(tproxy_conn_t *conn, struct tailhead *conn_list,
struct tailhead *close_list){
conn->state = CONN_CLOSED;
TAILQ_REMOVE(conn_list, conn, conn_ptrs);
TAILQ_INSERT_TAIL(close_list, conn, conn_ptrs);
}
static const char *http_split_methods[]={"GET /","POST /","HEAD /","OPTIONS /",NULL};
static const char *http_split_host[]={"\r\nHost: ",NULL};
bool handle_epollin(tproxy_conn_t *conn,int *data_transferred){
int numbytes;
int fd_in, fd_out;
bool bOutgoing;
ssize_t rd=0,wr=0;
//Easy way to determin which socket is ready for reading
//TODO: Optimize. This one allows me quick lookup for conn, but
//I need to make a system call to determin which socket
numbytes=0;
if(ioctl(conn->local_fd, FIONREAD, &numbytes) != -1
&& numbytes > 0){
fd_in = conn->local_fd;
fd_out = conn->remote_fd;
bOutgoing = true;
} else {
fd_in = conn->remote_fd;
fd_out = conn->local_fd;
numbytes=0;
ioctl(fd_in, FIONREAD, &numbytes);
bOutgoing = false;
}
if (numbytes)
{
if (bOutgoing)
{
char buf[8192],*p;
ssize_t l,split_pos=0;
const char **split_array,**split_item;
rd = recv(fd_in,buf,sizeof(buf),MSG_DONTWAIT);
if (rd>0)
{
switch (params.split_http_req)
{
case split_method:
split_array = http_split_methods;
break;
case split_host:
split_array = http_split_host;
break;
default:
split_array = NULL;
}
if (split_array)
{
for(split_item=split_array;*split_item;split_item++)
{
l = strlen(*split_item);
if (p=find_bin(buf,rd,*split_item,l))
{
split_pos = p-buf;
printf("Found split item '%s' at pos %d\n",*split_item,split_pos);
split_pos += l-1;
}
}
}
if (params.hostcase)
{
if (p=find_bin(buf,rd,"\r\nHost: ",8))
{
printf("Changing 'Host:' => 'host:' at pos %d\n",p-buf);
p[2]='h';
}
}
if (params.methodcase)
{
for(split_item=http_split_methods;*split_item;split_item++)
{
l = strlen(*split_item);
if (p=find_bin(buf,rd,*split_item,l))
{
printf("Changing '%s' case\n",*split_item);
*p += 'a'-'A';
break;
}
}
}
if (split_pos)
{
wr=send_with_flush(fd_out,buf,split_pos,0);
if (wr>=0)
wr=send(fd_out,buf+split_pos,rd-split_pos,0);
}
else
{
wr=send(fd_out,buf,rd,0);
}
}
}
else
{
// *** we are not interested in incoming traffic
// splice it without processing
//printf("splicing numbytes=%d\n",numbytes);
rd = numbytes = splice(fd_in, NULL, conn->splice_pipe[1], NULL,
SPLICE_LEN, SPLICE_F_MOVE | SPLICE_F_NONBLOCK);
//printf("spliced rd=%d\n",rd);
if (rd>0)
{
wr = splice(conn->splice_pipe[0], NULL, fd_out, NULL,
rd, SPLICE_F_MOVE);
}
//printf("splice rd=%d wr=%d\n",rd,wr);
}
}
if (data_transferred) *data_transferred = rd<0 ? 0 : rd;
return rd!=-1 && wr!=-1;
}
void remove_closed_connections(struct tailhead *close_list){
tproxy_conn_t *conn = NULL;
while(close_list->tqh_first != NULL){
conn = (tproxy_conn_t*) close_list->tqh_first;
TAILQ_REMOVE(close_list, close_list->tqh_first, conn_ptrs);
int rd=0;
while(handle_epollin(conn,&rd) && rd);
printf("Socket %d and %d closed, connection removed\n",
conn->local_fd, conn->remote_fd);
free_conn(conn);
}
}
int event_loop(int listen_fd){
int retval = 0, num_events = 0;
int tmp_fd = 0; //Used to temporarily hold the accepted file descriptor
tproxy_conn_t *conn = NULL;
int efd, i;
struct epoll_event ev, events[MAX_EPOLL_EVENTS];
struct tailhead conn_list, close_list;
uint8_t check_close = 0;
int conncount = 0;
//Initialize queue (remember that TAILQ_HEAD just defines the struct)
TAILQ_INIT(&conn_list);
TAILQ_INIT(&close_list);
if((efd = epoll_create(1)) == -1){
perror("epoll_create");
return -1;
}
//Start monitoring listen socket
memset(&ev, 0, sizeof(ev));
ev.events = EPOLLIN;
//There is only one listen socket, and I want to use ptr in order to have
//easy access to the connections. So if ptr is NULL that means an event on
//listen socket.
ev.data.ptr = NULL;
if(epoll_ctl(efd, EPOLL_CTL_ADD, listen_fd, &ev) == -1){
perror("epoll_ctl (listen socket)");
return -1;
}
while(1){
if((num_events = epoll_wait(efd, events, MAX_EPOLL_EVENTS, -1)) == -1){
perror("epoll_wait");
retval = -1;
break;
}
for(i=0; i<num_events; i++){
if(events[i].data.ptr == NULL){
//Accept new connection
tmp_fd = accept(listen_fd, NULL, 0);
if (tmp_fd<0)
{
fprintf(stderr, "Failed to accept connection\n");
}
else if (conncount>=params.maxconn)
{
close(tmp_fd);
fprintf(stderr, "Too much connections : %d\n",conncount);
}
else if((conn = add_tcp_connection(efd, &conn_list, tmp_fd, params.port)) == NULL)
{
close(tmp_fd);
fprintf(stderr, "Failed to add connection\n");
}
else
{
conncount++;
printf("Connections : %d\n",conncount);
}
} else {
conn = (tproxy_conn_t*) events[i].data.ptr;
//Only applies to remote_fd, connection attempt has
//succeeded/failed
if(events[i].events & EPOLLOUT){
if(check_connection_attempt(conn, efd) == -1){
fprintf(stderr, "Connection attempt failed for %d\n",
conn->remote_fd);
check_close = 1;
close_tcp_conn(conn, &conn_list, &close_list);
conncount--;
}
continue;
} else if(conn->state != CONN_CLOSED &&
(events[i].events & EPOLLRDHUP ||
events[i].events & EPOLLHUP ||
events[i].events & EPOLLERR)){
check_close = 1;
close_tcp_conn(conn, &conn_list, &close_list);
conncount--;
continue;
}
//Since I use an event cache, earlier events might cause for
//example this connection to be closed. No need to process fd if
//that is the case
if(conn->state == CONN_CLOSED){
continue;
}
if (!handle_epollin(conn,NULL)){
close_tcp_conn(conn, &conn_list, &close_list);
conncount--;
check_close = 1;
}
}
}
//Remove connections
if(check_close)
remove_closed_connections(&close_list);
check_close = 0;
}
//Add cleanup
return retval;
}
int8_t block_sigpipe(){
sigset_t sigset;
memset(&sigset, 0, sizeof(sigset));
//Get the old sigset, add SIGPIPE and update sigset
if(sigprocmask(SIG_BLOCK, NULL, &sigset) == -1){
perror("sigprocmask (get)");
return -1;
}
if(sigaddset(&sigset, SIGPIPE) == -1){
perror("sigaddset");
return -1;
}
if(sigprocmask(SIG_BLOCK, &sigset, NULL) == -1){
perror("sigprocmask (set)");
return -1;
}
return 0;
}
void exithelp()
{
printf(" --bind-addr=<ipv4_addr>|<ipv6_addr>\n --port=<port>\n --maxconn=<max_connections>\n --split-http-req=method|host\n --hostcase\t\t; change Host: => host:\n --methodcase\t\t; change GET => gET, POST=>pOST, ...\n --daemon\t\t; daemonize\n --user=<username>\t; drop root privs\n");
exit(1);
}
void parse_params(int argc, char *argv[])
{
int option_index=0;
int v,i;
memset(&params,0,sizeof(params));
params.maxconn = DEFAULT_MAX_CONN;
const struct option long_options[] = {
{"help",no_argument,0,0},// optidx=0
{"h",no_argument,0,0},// optidx=1
{"bind-addr",required_argument,0,0},// optidx=2
{"port",required_argument,0,0},// optidx=3
{"daemon",no_argument,0,0},// optidx=4
{"user",required_argument,0,0},// optidx=5
{"maxconn",required_argument,0,0},// optidx=6
{"hostcase",no_argument,0,0},// optidx=7
{"methodcase",no_argument,0,0},// optidx=8
{"split-http-req",required_argument,0,0},// optidx=9
{NULL,0,NULL,0}
};
while ((v=getopt_long_only(argc,argv,"",long_options,&option_index))!=-1)
{
if (v) exithelp();
switch(option_index)
{
case 0:
case 1:
exithelp();
break;
case 2: /* bind-addr */
strncpy(params.bindaddr,optarg,sizeof(params.bindaddr));
params.bindaddr[sizeof(params.bindaddr)-1] = 0;
break;
case 3: /* qnum */
i=atoi(optarg);
if (i<=0 || i>65535)
{
fprintf(stderr,"bad port number\n");
exit(1);
}
params.port=(uint16_t)i;
break;
case 4: /* daemon */
params.daemon = true;
break;
case 5: /* user */
{
struct passwd *pwd = getpwnam(optarg);
if (!pwd)
{
fprintf(stderr,"non-existent username supplied\n");
exit(1);
}
params.uid = pwd->pw_uid;
params.gid = pwd->pw_gid;
break;
}
case 6: /* maxconn */
params.maxconn=atoi(optarg);
if (params.maxconn<=0)
{
fprintf(stderr,"bad maxconn\n");
exit(1);
}
break;
case 7: /* hostcase */
params.hostcase = true;
break;
case 8: /* methodcase */
params.methodcase = true;
break;
case 9: /* split-http-req */
if (!strcmp(optarg,"method"))
params.split_http_req = split_method;
else if (!strcmp(optarg,"host"))
params.split_http_req = split_host;
else
{
fprintf(stderr,"Invalid argument for split-http-req\n");
exit(1);
}
break;
}
}
if (!params.port)
{
fprintf(stderr,"Need port number\n");
exit(1);
}
}
void daemonize()
{
int pid;
pid = fork();
if (pid == -1)
{
perror("fork: ");
exit(2);
}
else if (pid != 0)
exit(0);
if (setsid() == -1)
exit(2);
if (chdir ("/") == -1)
exit(2);
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
/* redirect fd's 0,1,2 to /dev/null */
open ("/dev/null", O_RDWR);
/* stdin */
dup(0);
/* stdout */
dup(0);
/* stderror */
}
bool droproot()
{
if (params.uid)
{
if (setgid(params.gid))
{
perror("setgid: ");
return false;
}
if (setuid(params.uid))
{
perror("setuid: ");
return false;
}
}
return true;
}
int main(int argc, char *argv[]){
int listen_fd = 0;
int yes = 1, retval = 0;
int r;
struct sockaddr_storage salisten;
socklen_t salisten_len;
int ipv6_only;
parse_params(argc,argv);
memset(&salisten,0,sizeof(salisten));
if (*params.bindaddr)
{
if (inet_pton(AF_INET,params.bindaddr, &((struct sockaddr_in*)&salisten)->sin_addr))
{
salisten.ss_family = AF_INET;
((struct sockaddr_in*)&salisten)->sin_port = htons(params.port);
salisten_len = sizeof(struct sockaddr_in);
}
else if (inet_pton(AF_INET6,params.bindaddr, &((struct sockaddr_in6*)&salisten)->sin6_addr))
{
salisten.ss_family = AF_INET6;
((struct sockaddr_in6*)&salisten)->sin6_port = htons(params.port);
salisten_len = sizeof(struct sockaddr_in6);
ipv6_only=1;
}
else
{
printf("bad bind addr\n");
exit(1);
}
}
else
{
salisten.ss_family = AF_INET6;
((struct sockaddr_in6*)&salisten)->sin6_port = htons(params.port);
salisten_len = sizeof(struct sockaddr_in6);
ipv6_only=0;
// leave sin6_addr zero
}
if (params.daemon) daemonize();
if((listen_fd = socket(salisten.ss_family, SOCK_STREAM, 0)) == -1){
perror("socket: ");
exit(EXIT_FAILURE);
}
if ((salisten.ss_family==AF_INET6) && setsockopt(listen_fd, IPPROTO_IPV6, IPV6_V6ONLY, &ipv6_only, sizeof(ipv6_only)) == -1)
{
perror("setsockopt (IPV6_ONLY): ");
close(listen_fd);
exit(EXIT_FAILURE);
}
if(setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) == -1)
{
perror("setsockopt (SO_REUSEADDR): ");
close(listen_fd);
exit(EXIT_FAILURE);
}
//Mark that this socket can be used for transparent proxying
//This allows the socket to accept connections for non-local IPs
if(setsockopt(listen_fd, SOL_IP, IP_TRANSPARENT, &yes, sizeof(yes)) == -1)
{
perror("setsockopt (IP_TRANSPARENT): ");
close(listen_fd);
exit(EXIT_FAILURE);
}
if (!droproot())
{
close(listen_fd);
exit(EXIT_FAILURE);
}
if(bind(listen_fd, (struct sockaddr *)&salisten, salisten_len) == -1){
perror("bind: ");
close(listen_fd);
exit(EXIT_FAILURE);
}
if(listen(listen_fd, BACKLOG) == -1){
perror("listen: ");
close(listen_fd);
exit(EXIT_FAILURE);
}
//splice() causes the process to receive the SIGPIPE-signal if one part (for
//example a socket) is closed during splice(). I would rather have splice()
//fail and return -1, so blocking SIGPIPE.
if(block_sigpipe() == -1){
fprintf(stderr, "Could not block SIGPIPE signal\n");
close(listen_fd);
exit(EXIT_FAILURE);
}
fprintf(stderr, "Will listen to port %d\n", params.port);
retval = event_loop(listen_fd);
close(listen_fd);
fprintf(stderr, "Will exit\n");
if(retval < 0)
exit(EXIT_FAILURE);
else
exit(EXIT_SUCCESS);
}

37
tpws/tpws.h Normal file
View File

@ -0,0 +1,37 @@
#ifndef TPROXY_EXAMPLE_H
#define TPROXY_EXAMPLE_H
#include <stdint.h>
#include <sys/queue.h>
#define BACKLOG 10
#define MAX_EPOLL_EVENTS BACKLOG
#define IP_TRANSPARENT 19 //So that application compiles on OpenWRT
#define SPLICE_LEN 65536
#define DEFAULT_MAX_CONN 512
//Three different states of a connection
enum{
CONN_AVAILABLE=0,
CONN_CLOSED,
};
typedef uint8_t conn_state_t;
struct tproxy_conn{
int local_fd; //Connection to host on local network
int remote_fd; //Connection to remote host
int splice_pipe[2]; //Have pipes per connection for now. Multiplexing
//different connections onto pipes is tricky, for
//example when flushing pipe after one connection has
//failed.
conn_state_t state;
//Create the struct which contains ptrs to next/prev element
TAILQ_ENTRY(tproxy_conn) conn_ptrs;
};
typedef struct tproxy_conn tproxy_conn_t;
//Define the struct tailhead (code in sys/queue.h is quite intuitive)
//Use tail queue for efficient delete
TAILQ_HEAD(tailhead, tproxy_conn);
#endif

285
tpws/tpws_conn.c Normal file
View File

@ -0,0 +1,285 @@
#include <stdio.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/ip.h>
#include <errno.h>
#include <string.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <stdlib.h>
#include "linux/netfilter_ipv4.h"
#include <ifaddrs.h>
#include "tpws_conn.h"
#ifndef IP6T_SO_ORIGINAL_DST
#define IP6T_SO_ORIGINAL_DST 80
#endif
int linger(int sock_fd)
{
struct linger ling={1,5};
return setsockopt(sock_fd,SOL_SOCKET,SO_LINGER,&ling,sizeof(ling));
}
bool ismapped(const struct sockaddr_in6 *sa)
{
// ::ffff:1.2.3.4
return !memcmp(sa->sin6_addr.s6_addr,"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff",12);
}
bool mappedcmp(const struct sockaddr_in *sa1,const struct sockaddr_in6 *sa2)
{
return ismapped(sa2) && !memcmp(sa2->sin6_addr.s6_addr+12,&sa1->sin_addr.s_addr,4);
}
bool sacmp(const struct sockaddr *sa1,const struct sockaddr *sa2)
{
return sa1->sa_family==AF_INET && sa2->sa_family==AF_INET && !memcmp(&((struct sockaddr_in*)sa1)->sin_addr,&((struct sockaddr_in*)sa2)->sin_addr,sizeof(struct in_addr)) ||
sa1->sa_family==AF_INET6 && sa2->sa_family==AF_INET6 && !memcmp(&((struct sockaddr_in6*)sa1)->sin6_addr,&((struct sockaddr_in6*)sa2)->sin6_addr,sizeof(struct in6_addr)) ||
sa1->sa_family==AF_INET && sa2->sa_family==AF_INET6 && mappedcmp((struct sockaddr_in*)sa1,(struct sockaddr_in6*)sa2) ||
sa1->sa_family==AF_INET6 && sa2->sa_family==AF_INET && mappedcmp((struct sockaddr_in*)sa2,(struct sockaddr_in6*)sa1);
}
uint16_t saport(const struct sockaddr *sa)
{
return htons(sa->sa_family==AF_INET ? ((struct sockaddr_in*)sa)->sin_port :
sa->sa_family==AF_INET6 ? ((struct sockaddr_in6*)sa)->sin6_port : 0);
}
// -1 = error, 0 = not local, 1 = local
int check_local_ip(const struct sockaddr *saddr)
{
struct ifaddrs *addrs,*a;
if (getifaddrs(&addrs)<0) return -1;
a = addrs;
while (a)
{
if (a->ifa_addr && sacmp(a->ifa_addr,saddr))
{
freeifaddrs(addrs);
return 1;
}
a = a->ifa_next;
}
freeifaddrs(addrs);
return 0;
}
//Createas a socket and initiates the connection to the host specified by
//remote_addr.
//Returns 0 if something fails, >0 on success (socket fd).
static int connect_remote(struct sockaddr_storage *remote_addr){
int remote_fd = 0, yes = 1;
//Use NONBLOCK to avoid slow connects affecting the performance of other
//connections
if((remote_fd = socket(remote_addr->ss_family, SOCK_STREAM |
SOCK_NONBLOCK, 0)) < 0){
perror("socket (connect_remote): ");
return 0;
}
if(setsockopt(remote_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) < 0){
perror("setsockopt (SO_REUSEADDR, connect_remote): ");
close(remote_fd);
return 0;
}
if(connect(remote_fd, (struct sockaddr*) remote_addr,
remote_addr->ss_family == AF_INET ? sizeof(struct sockaddr_in) :
sizeof(struct sockaddr_in6)) < 0){
if(errno != EINPROGRESS){
perror("connect (connect_remote): ");
close(remote_fd);
return 0;
}
}
return remote_fd;
}
//Store the original destination address in remote_addr
//Return 0 on success, <0 on failure
static int get_org_dstaddr(int sockfd, struct sockaddr_storage *orig_dst){
char orig_dst_str[INET6_ADDRSTRLEN];
socklen_t addrlen = sizeof(*orig_dst);
int r;
memset(orig_dst, 0, addrlen);
//For UDP transparent proxying:
//Set IP_RECVORIGDSTADDR socket option for getting the original
//destination of a datagram
// DNAT
r=getsockopt(sockfd, SOL_IP, SO_ORIGINAL_DST, (struct sockaddr*) orig_dst, &addrlen);
if (r<0)
r = getsockopt(sockfd, SOL_IPV6, IP6T_SO_ORIGINAL_DST, (struct sockaddr*) orig_dst, &addrlen);
if (r<0)
{
fprintf(stderr,"both SO_ORIGINAL_DST and IP6T_SO_ORIGINAL_DST failed !\n");
// TPROXY : socket is bound to original destination
r=getsockname(sockfd, (struct sockaddr*) orig_dst, &addrlen);
if (r<0)
{
perror("getsockname: ");
return -1;
}
}
if(orig_dst->ss_family == AF_INET){
inet_ntop(AF_INET,
&(((struct sockaddr_in*) orig_dst)->sin_addr),
orig_dst_str, INET_ADDRSTRLEN);
fprintf(stderr, "Original destination for socket %d : %s:%d\n", sockfd,orig_dst_str, htons(((struct sockaddr_in*) orig_dst)->sin_port));
} else if(orig_dst->ss_family == AF_INET6){
inet_ntop(AF_INET6,
&(((struct sockaddr_in6*) orig_dst)->sin6_addr),
orig_dst_str, INET6_ADDRSTRLEN);
fprintf(stderr, "Original destination for socket %d : [%s]:%d\n", sockfd,orig_dst_str, htons(((struct sockaddr_in6*) orig_dst)->sin6_port));
}
return 0;
}
//Acquires information, initiates a connect and initialises a new connection
//object. Return NULL if anything fails, pointer to object otherwise
tproxy_conn_t* add_tcp_connection(int efd, struct tailhead *conn_list,
int local_fd, uint16_t listen_port)
{
struct sockaddr_storage orig_dst;
tproxy_conn_t *conn;
int remote_fd;
struct epoll_event ev;
if(get_org_dstaddr(local_fd, &orig_dst)){
fprintf(stderr, "Could not get local address\n");
close(local_fd);
return NULL;
}
if (check_local_ip((struct sockaddr*)&orig_dst)==1 && saport((struct sockaddr*)&orig_dst)==listen_port)
{
fprintf(stderr, "Dropping connection to local address to the same port to avoid loop\n");
close(local_fd);
return NULL;
}
if((remote_fd = connect_remote(&orig_dst)) == 0){
fprintf(stderr, "Failed to connect\n");
close(remote_fd);
close(local_fd);
return NULL;
}
//Create connection object and fill in information
if((conn = (tproxy_conn_t*) malloc(sizeof(tproxy_conn_t))) == NULL){
fprintf(stderr, "Could not allocate memory for connection\n");
close(remote_fd);
close(local_fd);
return NULL;
}
memset(conn, 0, sizeof(tproxy_conn_t));
conn->state = CONN_AVAILABLE;
conn->remote_fd = remote_fd;
conn->local_fd = local_fd;
if(pipe(conn->splice_pipe) != 0){
fprintf(stderr, "Could not create the required pipe\n");
free_conn(conn);
return NULL;
}
//remote_fd is connecting. Non-blocking connects are signaled as done by
//socket being marked as ready for writing
memset(&ev, 0, sizeof(ev));
ev.events = EPOLLIN | EPOLLOUT;
ev.data.ptr = (void*) conn;
if(epoll_ctl(efd, EPOLL_CTL_ADD, remote_fd, &ev) == -1){
perror("epoll_ctl (remote_fd)");
free_conn(conn);
return NULL;
}
//Local socket can be closed while waiting for connection attempt. I need
//to detect this when waiting for connect() to complete. However, I dont
//want to get EPOLLIN-events, as I dont want to receive any data before
//remote connection is established
ev.events = EPOLLRDHUP;
if(epoll_ctl(efd, EPOLL_CTL_ADD, local_fd, &ev) == -1){
perror("epoll_ctl (local_fd)");
free_conn(conn);
return NULL;
} else
{
TAILQ_INSERT_HEAD(conn_list, conn, conn_ptrs);
return conn;
}
}
//Free resources occupied by this connection
void free_conn(tproxy_conn_t *conn){
close(conn->remote_fd);
close(conn->local_fd);
if(conn->splice_pipe[0] != 0){
close(conn->splice_pipe[0]);
close(conn->splice_pipe[1]);
}
free(conn);
}
//Checks if a connection attempt was successful or not
//Returns 0 if successfull, -1 if not
int8_t check_connection_attempt(tproxy_conn_t *conn, int efd){
struct epoll_event ev;
int conn_success = 0;
int fd_flags = 0;
socklen_t optlen = sizeof(conn_success);
//If the connection was sucessfull or not is contained in SO_ERROR
if(getsockopt(conn->remote_fd, SOL_SOCKET, SO_ERROR, &conn_success,
&optlen) == -1){
perror("getsockopt (SO_ERROR)");
return -1;
}
if(conn_success == 0){
fprintf(stderr, "Socket %d connected\n", conn->remote_fd);
//Set socket as blocking now, for ease of processing
//TODO: Non-blocking
if((fd_flags = fcntl(conn->remote_fd, F_GETFL)) == -1){
perror("fcntl (F_GETFL)");
return -1;
}
if(fcntl(conn->remote_fd, F_SETFL, fd_flags & ~O_NONBLOCK) == -1){
perror("fcntl (F_SETFL)");
return -1;
}
//Update both file descriptors. I am interested in EPOLLIN (if there is
//any data) and EPOLLRDHUP (remote peer closed socket). As this is just
//an example, EPOLLOUT is ignored and it is OK for send() to block
memset(&ev, 0, sizeof(ev));
ev.events = EPOLLIN | EPOLLRDHUP;
ev.data.ptr = (void*) conn;
if(epoll_ctl(efd, EPOLL_CTL_MOD, conn->remote_fd, &ev) == -1 ||
epoll_ctl(efd, EPOLL_CTL_MOD, conn->local_fd, &ev) == -1){
perror("epoll_ctl (check_connection_attempt)");
return -1;
} else {
return 0;
}
}
return -1;
}

13
tpws/tpws_conn.h Normal file
View File

@ -0,0 +1,13 @@
#ifndef TPROXY_TEST_CONN_H
#define TPROXY_TEST_CONN_H
#include "tpws.h"
#include <stdbool.h>
int check_local_ip(const struct sockaddr *saddr);
uint16_t saport(const struct sockaddr *sa);
tproxy_conn_t* add_tcp_connection(int efd, struct tailhead *conn_list,
int local_fd, uint16_t listen_port);
void free_conn(tproxy_conn_t *conn);
int8_t check_connection_attempt(tproxy_conn_t *conn, int efd);
#endif