Pandorabox解決IPV6中繼失敗(OpenWRT可能適用)


本隨筆記錄了在Pandorabox固件下(OpenWRT也適用)IPV6中繼失效問題的踩坑和解決過程。

路由器:Newifi 3

固件版本:PandoraBox 19.02

零、故障描述

參考配置了dhcp服務器,發現后台設備無法獲取到IPV6地址,拓撲如下:

電信光貓——路由器——PC

路由器/etc/config/dhcp內容如下:

 

config dnsmasq
        option domainneeded '1'
        option boguspriv '1'
        option filterwin2k '0'
        option localise_queries '1'
        option rebind_protection '0'
        option rebind_localhost '1'
        option local '/lan/'
        option domain 'lan'
        option expandhosts '1'
        option nonegcache '0'
        option authoritative '1'
        option noping '0'
        option readethers '1'
        option leasefile '/tmp/dhcp.leases'
        option resolvfile '/tmp/resolv.conf.auto'
        option localservice '1'
        option allservers '1'
        option sequential_ip '1'
        option redirect_all '0'
        option noresolv '0'
        option ignore '1'
        list server '127.0.0.1#5333'

config dhcp 'lan'
        option interface 'lan'
        option start '100'
        option limit '150'
        option leasetime '12h'
        option dhcpv6 'relay'
        option ndp 'relay'
        option ra 'relay'

config dhcp 'wan6'
        option interface 'wan6'
        option master '1'
        option dhcpv6 'relay'
        option ndp 'relay'
        option ra 'relay'

config odhcpd 'odhcpd'
        option maindhcp '0'
        option leasefile '/tmp/hosts/odhcpd'
        option leasetrigger '/usr/sbin/odhcpd-update'
        option loglevel '4'
dhcp配置文件

 

一、初查,定位故障點

通過進入光貓后台,發現光貓支持兩種方式分配IP地址,DHCPv6和SLACC兩種方式,目前使用DHCPv6方式。

調整為SLACC發現可以實現IP地址的獲取,遂定位問題點出在DHCPv6報文的中繼轉發上。

通過路由器安裝tcpdump抓包,發現路由器在lan側收到了報文,在wan側存在中繼請求和中繼應答報文,但是lan側沒有轉發應答報文,判斷中繼應答報文的轉發出現異常

 lan端捕獲到了請求報文

wan端有捕獲到中繼應答報文

二、編譯、調試odhcpd

因為使用的是Pandorabox的固件,需要到官網下載相關的SDK才可以進行編譯,所幸下載地址http://downloads.pangubox.com:6380/尚可訪問,成功下載到了SDK。

進入對應版本的SDK,替換對應的mirror為前面提到的地址,執行一次make,下載完所需的依賴后,完成了編譯前的准備工作。

進入github,找了OpenWRT組織維護的odhcpd代碼,下載了一份最新版本,同時從archive路徑下扣了package.mk文件放置到SDK的package目錄下,參考了一下archive里面UCI的MAKEFILE,結合自己的摸索,改了下MAKEFILE以支持odhcpd的代碼編譯。

 1 #
 2 # Copyright (C) 2008-2014 OpenWrt.org
 3 #
 4 # This is free software, licensed under the GNU General Public License v2.
 5 # See /LICENSE for more information.
 6 #
 7 
 8 include $(TOPDIR)/rules.mk
 9 
10 PKG_NAME:=odhcpd
11 PKG_VERSION:=1.11-3
12 PKG_BUILD_PARALLEL:=0
13 
14 include $(INCLUDE_DIR)/package.mk
15 include $(INCLUDE_DIR)/cmake.mk
16 
17 
18 CMAKE_OPTIONS = \
19         -DUBUS=1 \
20         -DCMAKE_VERBOSE_MAKEFILE:BOOL=ON
21 
22 define Package/odhcpd
23   SECTION:=base
24   CATEGORY:=Base system
25   DEPENDS:=+libnl-tiny +libubox +libuci +libubus
26   TITLE:=Odhcpd for OpenWRT
27 endef
28 
29 TARGET_CFLAGS += -I$(STAGING_DIR)/usr/include
30 TARGET_LDFLAGS += -L$(STAGING_DIR)/usr/lib
31 
32 define Package/odhcpd/install
33         $(INSTALL_DIR) $(1)/sbin
34         $(INSTALL_BIN) $(PKG_BUILD_DIR)/odhcpd $(1)/sbin/
35 endef
36 
37 $(eval $(call BuildPackage,odhcpd))
Makefile文件

很明顯的,編譯不報錯,是不可能的。編譯報錯發現缺少了mkdir_p的函數實現,參考了1.11版本的odhcpd代碼,移植了相關實現,解決了此問題,移植代碼如下:

 1 static int mkdir_p(char *dir, mode_t mask)
 2 {
 3         char *l = strrchr(dir, '/');
 4         int ret;
 5 
 6         if (!l)
 7                 return 0;
 8 
 9         *l = '\0';
10 
11         if (mkdir_p(dir, mask))
12                 return -1;
13 
14         *l = '/';
15 
16         ret = mkdir(dir, mask);
17         if (ret && errno == EEXIST)
18                 return 0;
19 
20         if (ret)
21                 syslog(LOG_ERR, "mkdir(%s, %d) failed: %m\n", dir, mask);
22 
23         return ret;
24 }
View Code

編譯后調試沒有坑也是不可能的,調試打了斷點結果發現我竟然沒法進入函數,參考代碼發現程序文件中充斥着大量的static聲明,參考GNU GCC手冊通過增加一個編譯選項(-fno-keep-static-consts)解決了問題,所以對CMakeLists.txt進行了改動,改動如下:

set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -O0 -Wall -ggdb -fno-keep-static-consts -std=gnu99")

編譯完成后,得到了一個可調試的,版本還很新的odhcp服務器程序。

通過斷點調試,發現在eth0.2接口上沒有收到報文,特別奇怪,另外一邊的br-lan可以收到報文,結合tcpdump的抓包結果,懷疑套接字收包存在異常。

三、編寫測試程序,驗證並鎖定原因

閱讀了odhcpd有關代碼,參考網上的udp socket編程實現(不好意思,我真的太菜了),從dhcpv6_setup_interface函數中抽取相關的socket創建和bind代碼,組成了調試小程序,代碼如下:

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <stdbool.h>
#include <netinet/ether.h>
#include <net/if.h>

#define DHCPV6_CLIENT_PORT 546
#define DHCPV6_SERVER_PORT 547
#define DHCPV6_HOP_COUNT_LIMIT 32

#define ALL_DHCPV6_RELAYS "ff02::1:2"
#define ALL_DHCPV6_SERVERS "ff05::1:3"

#define BUFF_LEN 9000

struct socket_st {
        int fd;
        char ifname[20];
};

int socket_create(struct socket_st *sock)
{
        int ret = 0;
        int ifindex;

        if (sock->fd >= 0) {
                printf("close socket %d\n", sock->fd);
                close(sock->fd);
                sock->fd = -1;
        }

        /* Configure multicast settings */
        struct sockaddr_in6 bind_addr = {AF_INET6, htons(DHCPV6_SERVER_PORT),
                                0, IN6ADDR_ANY_INIT, 0};
        struct ipv6_mreq mreq;
        int val = 1;

        sock->fd = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, IPPROTO_UDP);
        if (sock->fd < 0) {
                printf("socket(AF_INET6): %m\n");
                ret = -1;
                goto out;
        }

        /* Basic IPv6 configuration */
        if (setsockopt(sock->fd, SOL_SOCKET, SO_BINDTODEVICE,
                                sock->ifname, strlen(sock->ifname)) < 0) {
                printf("setsockopt(SO_BINDTODEVICE): %m\n");
                ret = -1;
                goto out;
        }

        if (setsockopt(sock->fd, IPPROTO_IPV6, IPV6_V6ONLY,
                                &val, sizeof(val)) < 0) {
                printf("setsockopt(IPV6_V6ONLY): %m\n");
                ret = -1;
                goto out;
        }

        if (setsockopt(sock->fd, SOL_SOCKET, SO_REUSEADDR,
                                &val, sizeof(val)) < 0) {
                printf("setsockopt(SO_REUSEADDR): %m\n");
                ret = -1;
                goto out;
        }

        if (setsockopt(sock->fd, IPPROTO_IPV6, IPV6_RECVPKTINFO,
                                &val, sizeof(val)) < 0) {
                printf("setsockopt(IPV6_RECVPKTINFO): %m\n");
                ret = -1;
                goto out;
        }

        val = DHCPV6_HOP_COUNT_LIMIT;
        if (setsockopt(sock->fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS,
                                &val, sizeof(val)) < 0) {
                printf("setsockopt(IPV6_MULTICAST_HOPS): %m\n");
                ret = -1;
                goto out;
        }

        val = 0;
        if (setsockopt(sock->fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP,
                                &val, sizeof(val)) < 0) {
                printf("setsockopt(IPV6_MULTICAST_LOOP): %m\n");
                ret = -1;
                goto out;
        }

        if (bind(sock->fd, (struct sockaddr*)&bind_addr,
                                sizeof(bind_addr)) < 0) {
                printf("bind(): %m\n");
                ret = -1;
                goto out;
        }

        ifindex = if_nametoindex(sock->ifname);
        if (ifindex) {
                memset(&mreq, 0, sizeof(mreq));
                inet_pton(AF_INET6, ALL_DHCPV6_RELAYS, &mreq.ipv6mr_multiaddr);
                mreq.ipv6mr_interface = ifindex;

                if (setsockopt(sock->fd, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP,
                                        &mreq, sizeof(mreq)) < 0) {
                        printf("setsockopt(IPV6_ADD_MEMBERSHIP): %m\n");
                        ret = -1;
                        goto out;
                }
        } else {
                printf("get ifindex failed! ifname:%s\n", sock->ifname);
        }


out:
        if (ret < 0 && sock->fd >= 0) {
                close(sock->fd);
                sock->fd = -1;
        }

        return ret;
}

int socket_recv(struct socket_st *sock)
{
        struct sockaddr_in6 send_addr6;
        char buf[BUFF_LEN], str[INET6_ADDRSTRLEN];;
        int recv_size, buf_len;
        while(1) {
                memset(&send_addr6, 0, sizeof(send_addr6));
                recv_size=recvfrom(sock->fd, buf, BUFF_LEN, 0, (struct sockaddr *)&send_addr6, &buf_len);
                if(recv_size < 0){
                        printf("recvfrom error!\n");
                        return -1;
                }
                if(inet_ntop(AF_INET6, &send_addr6, str, INET6_ADDRSTRLEN) == NULL){
                        perror("inet ntop/n");
                        printf("error\n");
                }
                printf("send_addr6=%s\n", str);
        }
        return 0;
}

int main()
{
        struct socket_st sock;
        sock.fd = -1;
        strcpy(sock.ifname, "eth0.2");
        socket_create(&sock);

        socket_recv(&sock);

        return 0;
}
View Code

通過更換接口以及xcap構包發送,發現了很有意思的現象,就是其余變量不變,lan口怎么試都有報文,但是wan口死活收不到報文

突然一陣念頭飄過,防火牆!!!

將防火牆wan區域入站改為允許后,捕獲到了報文,並且PC成功獲取到了地址,這下原因水落石出。

處於安全考量,將入站流量重新設置為拒絕,需要修改防火牆配置/etc/config/firewall,參考報文結構,最終擬定放通目的為本機的,訪問udp6 547端口的報文入站

新增配置如下:

1 config rule
2         option target 'ACCEPT'
3         option src 'wan'
4         option name 'Allow-DHCPv6_Reply'
5         option family 'ipv6'
6         option proto 'udp'
7         option dest_port '547'
8         option limit '100/sec'
新增DHCPv6-Relay放通規則

 測試,通過!!!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM