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 '/f.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'
recv函数
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
进⼊对应版本的SDK,替换对应的mirror为前⾯提到的地址,执⾏⼀次make,下载完所需的依赖后,完成了编译前的准备⼯作。
进⼊github,了OpenWRT组织维护的odhcpd代码,下载了⼀份最新版本,同时从archive路径下扣了package.mk⽂件放置到SDK的package⽬录下,参考了⼀下archive⾥⾯UCI的MAKEFILE,结合⾃⼰的摸索,改了下MAKEFILE以⽀持odhcpd的代码编译。
1 #
2 # Copyright (C)
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代码,移植了相关实现,解决了此问题,移植代码如下:
1static int mkdir_p(char *dir, mode_t mask)
2 {
3char *l = strrchr(dir, '/');
4int ret;
5
6if (!l)
7return0;
8
9        *l = '\0';
10
11if (mkdir_p(dir, mask))
12return -1;
13
14        *l = '/';
15
16        ret = mkdir(dir, mask);
17if (ret && errno == EEXIST)
18return0;
19
20if (ret)
21                syslog(LOG_ERR, "mkdir(%s, %d) failed: %m\n", dir, mask);
22
23return ret;
24 }
View Code
编译后调试没有坑也是不可能的,调试打了断点结果发现我竟然没法进⼊函数,参考代码发现程序⽂件中充斥着⼤量的static声明,参考GNU GCC⼿冊通过增加⼀个编译选项(-fno-keep-static-consts)解决了问题,所以对进⾏了改动,改动如下:
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);
}
return0;
}
int main()
{
struct socket_st sock;
sock.fd = -1;
strcpy(sock.ifname, "eth0.2");
socket_create(&sock);
socket_recv(&sock);
return0;
}
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放通规则
测试,通过