Чиним hanstunnel на openwrt (вычисление контрольной суммы сетевого пакета)

    Всем привет. Наверное, многие тут знают прогу hanstunnel, которая позволяет поднять туннель поверх ICMP, а точнее поверх пингов. Решил я поставить ее себе на роутер под openwrt. Собрал, завел… Не работает. Симптомы были простые — пакет уходит с роутера во внешнюю сеть, а на адресата не приходит, при этом в домашней локалке все работает. При этом тот же hans, запущенный на домашнем компе подключается наружу без вопросов. Кому интересно, как собрать пакет hans под openwrt и как заставить его работать — велком под кат.



    Первый этап — сборка. О том, как поднять у себя на машине среду сборки неплохо написано на самом сайте 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
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 25

      +1
      я думаю было бы неплохо если вы бы засабмитили этот фикс
        0
        знать бы еще как…
          0
          github.com/friedrich/hans
          Форкаете, патчите код (обязательно закрыв изменения ифдефами), коммитите, делаете пулл реквест. Всё!
          • UFO just landed and posted this here
              0
              Во-первых огребёте с выравниванием, т.к. никто не сказал что пришедший указатель data — выравнен по 2 байта

              Оригинальный код на эту тему тоже не парится, так что норм.
              Во-вторых, нужно сделать код endianess-нейтральным, а не так как сейчас

              Я бы сделал так:
              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) {
                      char tail[2] = {*(char *)data16, 0};
                      sum += *(uint16_t *)tail;
                  }
                  sum = (sum >> 16) + (sum & 0xffff);
                  sum += (sum >> 16);
                  return ~sum;
              }
              
                0
                Согласен, если ntohs не макрос, то так будет лучше.
                • UFO just landed and posted this here
                    0
                    Традиционно один патч — одна проблема. Этот патч фиксит неправильно считаемую контрольную сумму.
                    Вы предлагаете патч для оптимизации. Обычно вместе с этим требуется какое-нибудь обоснование: цифры, показывающие улучшение.
                    • UFO just landed and posted this here
                        0
                        проблема одна — ф-ция не нейтральна к endianess + возможен доступ к невыравненным

                        Плюс между слагаемыми просто оттеняет то, что проблема одна…
                        • UFO just landed and posted this here
                      0
                      Т.е. если автор hanstunnel поговнокодил — можно говнокодить? Я хотя бы assert-ов добавил бы, на то что указатель выравненный.

                      Тогда уж надо вообще всю прогу рефакторить. Только вот с таким подходом все время уйдет на один сплошной рефакторинг.
                    0
                    Оригинал не смотрел

                    Не читал, но осуждаю (с)

                    Смотрите код целиком. Не будет gcc выделять по new блок с плохим выравниванием. Код и так endianess-нейтральный. Сетевой контрольной сумме пофигу на endian, а последний байт ставится на место через ntohs.
                    • UFO just landed and posted this here
                        0
                        Изменения на портабельность не повлияли. Допущения обеспечивает класс, которому принадлежит метод. Кстати, если код не портабелен, то как он работает на Intel, ARM и MIPS?
                        • UFO just landed and posted this here
                            0
                            Я просто пытаюсь донести, что этот код в том контексте, в котором он используется в приложении, не нуждается в дополнительных assert'ах и проверках. Без контекста и к i++ придраться можно, ибо, например, не thread-safe.
                            Кстати, если напишете тесты, которые покрашат hanstunnel на MIPS и ARM, будет интересно.
                            • UFO just landed and posted this here
                                0
                                Кстати, можно тогда посмотреть на пример вашего решения данной проблемы? То есть как бы на моем месте проблему решали вы? Имеется в виду в данном конкретном случае.
                                • UFO just landed and posted this here
                                    0
                                    Основное отличие тут — побайтовое чтение. Сам по себе порядок байтов абсолютно не важен в силу алгоритма подсчета.

                                    Теперь смотрим единственное использование метода.

                                        EchoHeader *header = (EchoHeader *)(sendBuffer + sizeof(IpHeader));
                                        header->type = reply ? 0: 8;
                                        header->code = 0;
                                        header->id = htons(id);
                                        header->seq = htons(seq);
                                        header->chksum = 0;
                                        header->chksum = icmpChecksum(sendBuffer + sizeof(IpHeader), payloadLength + sizeof(EchoHeader));
                                    


                                    где IpHeader имеет размер 20 байт, а sendBuffer выделен по new в конструкторе. То есть смещение всегда четное.

                                    Предлагаю сойтись на том, что проверки и побайтовое чтение нужны в общем случае, особенно, если код отдается как библиотека.
                                    • UFO just landed and posted this here
                                      +1
                                      Теоретическая часть по ссылке из поста отлично объясняет, почему суммирование с циклическим переносом работает одинаково и на be и на le. Рекомендую ознакомиться.

                                      TL;DR: смена порядка байт при чтении компенсируется сменой порядка байт при записи, а перенос всё равно циклический.
                                      • UFO just landed and posted this here
                0
                А можно готовый вариант в виде устанавливаемого пакета для чайников?

                Only users with full accounts can post comments. Log in, please.