Всем привет. Наверное, многие тут знают прогу hanstunnel, которая позволяет поднять туннель поверх ICMP, а точнее поверх пингов. Решил я поставить ее себе на роутер под openwrt. Собрал, завел… Не работает. Симптомы были простые — пакет уходит с роутера во внешнюю сеть, а на адресата не приходит, при этом в домашней локалке все работает. При этом тот же hans, запущенный на домашнем компе подключается наружу без вопросов. Кому интересно, как собрать пакет hans под openwrt и как заставить его работать — велком под кат.
Первый этап — сборка. О том, как поднять у себя на машине среду сборки неплохо написано на самом сайте openwrt, а так же в куче разных статей, поэтому данный кусок я оставлю за кадром. Предположим, что у нас есть среда с собранным toolchain'ом и она умеет собирать стандартные пакеты openwrt.
Для сборки пакета нам понадобится исходник hanstunnel, который можно взять тут, а также Makefile с описанием пакета, файл конфига и файл init-скрипта. Последние три случайно я нашел тут в виде описания патчей. Может, кто-нибудь найдет прямые ссылки, но мне и так подошло. Для сборки своих пакетов создаем какую-нибудь папку, например custom, в нее кладем папки будущих пакетов и наводим на них ссылки из package/feeds/packages. Я слегка подпилил Makefile для пакета, чтобы он собирал исходники их папки src, лежащей рядом,
Шапку Makefile'а сборки тоже пришлось чуть-чуть подпилить
Раскладываем файлы по местам, должно получиться что-то типа:
Дальше идем в корень buildroot и радостно набираем
Все, пакет готов, лежит он в папке bin рядом с остальными.
Осталось проверить. Вот тут меня ждал облом. С моим компом в домашней локалке туннель поднялся сразу без вопросов. Только я обрадовался и пошел проверять подключение снаружи, но, как описано выше, пакеты с openwrt уходили, а адресат их не видел. Та же ситуация возникала, если запустить hans на роутере в режиме клиента. После долгих раздумий я догадался снять дамп ICMP и поискать разницу между подключением с роутера и с моего компа. Проблема оказалась в контрольной сумме ICMP, о чем мне радостно сообщил wireshark. Я честно взял калькулятор и пошел сам вручную считать сумму по алгоритму, прописанному в echo.cpp, который очень похож на официальный. Получалось что угодно, но не то, что нужно. В итоге оказалось, что проблема в нечетной длине пакета и MIPS архитектуре. Добрые люди в RFC пишут, что последний байт надо просто прибавить к сумме, но на самом деле это не так. Прибавлять надо не последний байт, а два байта, второй из которых 0, что на Big-endian архитектурах означает прибавку нашего байта, помноженного на 256.
Вот какая в итоге получилась функция icmpChecksum:
С ней наконец-то все заработало как надо. Happy end.
UPD: поправил хвост на более красивый вариант от jcmvbkbc
Первый этап — сборка. О том, как поднять у себя на машине среду сборки неплохо написано на самом сайте openwrt, а так же в куче разных статей, поэтому данный кусок я оставлю за кадром. Предположим, что у нас есть среда с собранным toolchain'ом и она умеет собирать стандартные пакеты openwrt.
Для сборки пакета нам понадобится исходник hanstunnel, который можно взять тут, а также Makefile с описанием пакета, файл конфига и файл init-скрипта. Последние три случайно я нашел тут в виде описания патчей. Может, кто-нибудь найдет прямые ссылки, но мне и так подошло. Для сборки своих пакетов создаем какую-нибудь папку, например custom, в нее кладем папки будущих пакетов и наводим на них ссылки из package/feeds/packages. Я слегка подпилил Makefile для пакета, чтобы он собирал исходники их папки src, лежащей рядом,
вот, что получилось
# Copyright (C) 2006 OpenWrt.org
#
# This is free software, licensed under the GNU General Public License v2.
# See /LICENSE for more information.
#
# $Id: Makefile 6008 2007-01-06 18:39:10Z nbd $
include $(TOPDIR)/rules.mk
PKG_NAME:=hanstunnel
PKG_VERSION:=0.4.3
PKG_RELEASE:=1
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-$(PKG_VERSION)
include $(INCLUDE_DIR)/package.mk
define Package/hanstunnel
SECTION:=net
CATEGORY:=Network
SUBMENU:=Firewall Tunnel
DEPENDS:=+libstdcpp +kmod-tun
TITLE:=Hans IP over ICMP
URL:=http://code.gerade.org/hans/
endef
define Package/hanstunnel/description
Hans makes it possible to tunnel IPv4 through ICMP echo packets,
so you could call it a ping tunnel. This can be useful when you
find yourself in the situation that your Internet access is
firewalled, but pings are allowed. endef
endef
define Build/Prepare
echo PREPARE PREPARE
mkdir -p $(PKG_BUILD_DIR)
cp -r ./src/* $(PKG_BUILD_DIR)/
endef
define Build/Compile
$(MAKE) -C $(PKG_BUILD_DIR) GCC=$(TARGET_CC) GPP=$(TARGET_CXX)
endef
define Package/hanstunnel/install
$(INSTALL_DIR) $(1)/usr/sbin
$(INSTALL_BIN) $(PKG_BUILD_DIR)/hans $(1)/usr/sbin/
$(INSTALL_DIR) $(1)/etc/init.d
$(INSTALL_BIN) ./files/hans.init $(1)/etc/init.d/hans
$(INSTALL_DIR) $(1)/etc/config
$(INSTALL_CONF) ./files/hans.config $(1)/etc/config/hans
endef
$(eval $(call BuildPackage,hanstunnel))
Шапку Makefile'а сборки тоже пришлось чуть-чуть подпилить
Makefile сборки
#LDFLAGS += `sh osflags ld $(MODE)` CFLAGS += -c -g -DLINUX -DHAVE_LINUX_IF_TUN_H TUN_DEV_FILE = src/tun_dev_linux.c #GCC = gcc #GPP = g++ .PHONY: directories all: hans directories: build_dir build_dir: mkdir -p build tunemu.o: directories build/tunemu.o hans: directories build/tun.o build/sha1.o build/main.o build/client.o build/server.o build/auth.o build/worker.o build/time.o build/tun_dev.o build/echo.o build/exception.o build/utility.o $(GPP) -o hans build/tun.o build/sha1.o build/main.o build/client.o build/server.o build/auth.o build/worker.o build/time.o build/tun_dev.o build/echo.o build/exception.o build/utility.o $(LDFLAGS) build/utility.o: src/utility.cpp src/utility.h $(GPP) -c src/utility.cpp -o $@ -o $@ $(CFLAGS) build/exception.o: src/exception.cpp src/exception.h $(GPP) -c src/exception.cpp -o $@ $(CFLAGS) build/echo.o: src/echo.cpp src/echo.h src/exception.h $(GPP) -c src/echo.cpp -o $@ $(CFLAGS) build/tun.o: src/tun.cpp src/tun.h src/exception.h src/utility.h src/tun_dev.h $(GPP) -c src/tun.cpp -o $@ $(CFLAGS) build/tun_dev.o: $(GCC) -c $(TUN_DEV_FILE) -o build/tun_dev.o -o $@ $(CFLAGS) build/sha1.o: src/sha1.cpp src/sha1.h $(GPP) -c src/sha1.cpp -o $@ $(CFLAGS) build/main.o: src/main.cpp src/client.h src/server.h src/exception.h src/worker.h src/auth.h src/time.h src/echo.h src/tun.h src/tun_dev.h $(GPP) -c src/main.cpp -o $@ $(CFLAGS) build/client.o: src/client.cpp src/client.h src/server.h src/exception.h src/config.h src/worker.h src/auth.h src/time.h src/echo.h src/tun.h src/tun_dev.h $(GPP) -c src/client.cpp -o $@ $(CFLAGS) build/server.o: src/server.cpp src/server.h src/client.h src/utility.h src/config.h src/worker.h src/auth.h src/time.h src/echo.h src/tun.h src/tun_dev.h $(GPP) -c src/server.cpp -o $@ $(CFLAGS) build/auth.o: src/auth.cpp src/auth.h src/sha1.h src/utility.h $(GPP) -c src/auth.cpp -o $@ $(CFLAGS) build/worker.o: src/worker.cpp src/worker.h src/tun.h src/exception.h src/time.h src/echo.h src/tun_dev.h src/config.h $(GPP) -c src/worker.cpp -o $@ $(CFLAGS) build/time.o: src/time.cpp src/time.h $(GPP) -c src/time.cpp -o $@ $(CFLAGS) clean: rm -f build/tun.o build/sha1.o build/main.o build/client.o build/server.o build/auth.o build/worker.o build/time.o build/tun_dev.o build/echo.o build/exception.o build/utility.o build/tunemu.o hans rm -df build build/tunemu.o: src/tunemu.h src/tunemu.c $(GCC) -c src/tunemu.c -o build/tunemu.o
Раскладываем файлы по местам, должно получиться что-то типа:
$ls -R custom custom: hanstunnel custom/hanstunnel: files Makefile src custom/hanstunnel/files: hans.config hans.init custom/hanstunnel/src: Makefile osflags src custom/hanstunnel/src/src: auth.cpp client.h echo.h main.cpp sha1.cpp time.cpp tun_dev_darwin_emu.c tun_dev.h tun_dev_svr4.c tun.h worker.cpp auth.h config.h exception.cpp server.cpp sha1.h time.h tun_dev_freebsd.c tun_dev_linux.c tunemu.c utility.cpp worker.h client.cpp echo.cpp exception.h server.h sha1_license.txt tun.cpp tun_dev_generic.c tun_dev_openbsd.c tunemu.h utility.h
Дальше идем в корень buildroot и радостно набираем
$make package/feeds/packages/hanstunnel/compile -j5 make[1] package/feeds/packages/hanstunnel/compile make[2] -C package/kernel/linux compile make[2] -C package/libs/toolchain compile make[2] -C custom/hanstunnel compile
Все, пакет готов, лежит он в папке bin рядом с остальными.
Осталось проверить. Вот тут меня ждал облом. С моим компом в домашней локалке туннель поднялся сразу без вопросов. Только я обрадовался и пошел проверять подключение снаружи, но, как описано выше, пакеты с openwrt уходили, а адресат их не видел. Та же ситуация возникала, если запустить hans на роутере в режиме клиента. После долгих раздумий я догадался снять дамп ICMP и поискать разницу между подключением с роутера и с моего компа. Проблема оказалась в контрольной сумме ICMP, о чем мне радостно сообщил wireshark. Я честно взял калькулятор и пошел сам вручную считать сумму по алгоритму, прописанному в echo.cpp, который очень похож на официальный. Получалось что угодно, но не то, что нужно. В итоге оказалось, что проблема в нечетной длине пакета и MIPS архитектуре. Добрые люди в RFC пишут, что последний байт надо просто прибавить к сумме, но на самом деле это не так. Прибавлять надо не последний байт, а два байта, второй из которых 0, что на Big-endian архитектурах означает прибавку нашего байта, помноженного на 256.
Вот какая в итоге получилась функция icmpChecksum:
uint16_t Echo::icmpChecksum(const char *data, int length)
{
uint16_t *data16 = (uint16_t *)data;
uint32_t sum = 0;
for (sum = 0; length > 1; length -= 2)
sum += *data16++;
if (length == 1)
{
unsigned char tail[2] = {*(unsigned char *)data16, 0};
sum += *(uint16_t *)tail;
}
while (sum >> 16)
sum = (sum >> 16) + (sum & 0xffff);
return ~sum;
}
С ней наконец-то все заработало как надо. Happy end.
UPD: поправил хвост на более красивый вариант от jcmvbkbc
