1 介绍
1.1 适用人员
适用于使用shell脚本进行openwrt功能开发的开发人员
1.2 开发环境
siflower SDK,siflower硬件平台
1.3 相关背景
VLAN(Virtual LAN),即“虚拟局域网”,可以使用户自由地根据实际需要分割广播域,在openwrt上可通过配置network文件实现vlan划分。但并不是每一个用户都了解vlan划分流程,因此路由器在面向普通用户的时候,应当具备方便、简洁的特点:用户甚至不需要了解什么是vlan,怎么区分wan/lan口;随便插上网线就能实现上网功能。
1.4 功能概述
wan/lan 自适应具有三种配置wan口的功能:开机自动配置、立即自动配置、立即手动配置。自动配置时会根据当前的网线连接情况划分wan/lan口。需要更换wan口时,只需将网线插入路由器需要配置的端口,运行脚本自动配置,就会自动将wan重新划分到该端口上。配置完成后,路由器依旧可以正常上网。
2 项目引用
3 开发详情
3.1 基础指令及原理
3.1.1 uci指令
以太网相关配置是用network文件存储的,路径为/etc/config/network。我们可以直接手动修改该文件,也可以通过在串口下直接键入uci指令修改network配置,还可以在shell中编写程序执行uci指令实现一定的功能。uci常用指令有:
指令 | 作用 |
---|---|
uci get network.lan.ipaddr | 获取lan节点下的ip选项的值 |
uci set network.test=interface | network里加一个interface类型的节点 |
uci set network.test.a=”abc” | 向test节点下的a选项赋值(不存在a则创建此选项) |
uci set network.@switch_vlan[0].vlan=1 | 将第一个switch_vlan节点下的vlan值改为1 |
uci delete network.test.a | 删除a选项 |
uci delete network.test | 删除test节点 |
uci commit | 保存修改 |
更多地了解如何使用uci指令配置config文件,请参考config文件配置手册或openwrt uci官方文档
3.1.2 vlan划分
VLAN(Virtual LAN),即“虚拟局域网”,可以使用户自由地根据实际需要分割广播域。AC22镜像network配置中vlan相关部分如下所示:
config interface 'lan' option ifname 'eth0.1' option force_link '1' option macaddr '10:16:88:3a:8d:f4' option type 'bridge' option proto 'static' option ipaddr '192.168.4.1' option netmask '255.255.255.0' option ip6assign '60' option group '0' option rps_cpus '2' option xps_cpus '2' config interface 'wan' option ifname 'eth0.2' option force_link '1' option macaddr '10:16:88:3a:8d:f5' option rps_cpus '1' option xps_cpus '0' option proto 'dhcp' ... config switch option name 'switch0' option reset '1' option enable_vlan '1' config switch_vlan option device 'switch0' option vlan '1' option ports '0 1 2 3 16t' config switch_vlan option device 'switch0' option vlan '2' option ports '4 16t'
lan所在的eth0.1被划到了vlan 1,对应port 0 1 2 3,也就是第1,2,3,4号口。wan所在的eth0.2被划到了vlan 2,对应第5号口。如果想要将5号口划为lan,3号口划为wan,则只需将wan对应的ports改为’2 16t’,lan对应的ports改为’0 1 3 4 16t’。uci对应的指令为:
uci set network.@switch_vlan[0].ports='0 1 3 4 16t' uci set network.@switch_vlan[1].ports='2 16t' uci commit
更多vlan划分的内容,请参考以太网wan-lan划分指南。如果能划分出5个vlan分别对应5个路由器端口,那么就能通过向外发送dhcp discover数据包的方法寻找dhcp服务器,从而知道哪一个口应该成为wan口。
3.1.3 dhcp协议
详细了解dhcp协议过程,请参考维基百科-DHCP协议。wan/lan自适应流程会利用到其中的前两步:
1、从某端口向dhcp server发送discover数据包。
2、若dhcp server收到discover数据包,则会回复一个offer包。
至此,若收到offer包,即可判断该端口与dhcp server相连,可配置为路由器的wan口。
3.2 功能设计流程
3.2.1 自启动实现
openwrt在开机启动时,会自动执行路径为/etc/rc.local的文件。因此在rc.local的末尾部分加上运行某脚本的指令,那么这个脚本就会实现开机自启动。例如:现有一个可执行脚本auto_adapt.sh放在/bin目录下。如果在rc.local最后(exit 0之前)添加./bin/auto_adapt.sh,这个脚本就会开机自启动。若在脚本中加入对uci参数的判断(例如network.lan.auto_adapt参数为1时才执行脚本),则可以实现由用户随时设置是否开机自启动。
3.2.2 wan/lan自适应流程
1 下发自动配置指令或路由器开机启动时,将network备份并重新划分5个vlan分别对应5个端口,替代原有配置。
2 利用发包工具send从这5个端口向外发送dhcp discover包,若收到dhcp server的回复,则说明检测到wan口。
3 恢复network原有配置,并将检测到的端口配置为wan口。下发手动配置指令时直接进行这一步。
4 重启网络,自适应配置完成,路由器恢复上网功能。
graph TD A(自动配置指令) -->B(划分vlan) --> C(send检测wan口) --> D(配置WAN口) --> E(重启网络) F(开机启动) -->G(rc.local判断是否自动配置) --> |yes| B I(手动配置指令) -->D
3.3 代码实现(以AC22为例)
实现wan/lan自适应的demo放在package/network/services/auto_adapt路径下。需要使用时,在sdk根目录下运行make menuconfig,选中Network下的auto_adapt,保存退出后再进行编译。files里为脚本和网页,src下为send发包工具代码。
3.3.1 send.c
send是配合auto_adapt.sh脚本使用的发包工具,源码路径为package/network/services/auto_adapt/src/send.c。此工具可以指定端口发送dhcp disvover数据包。根据是否能收到dhcp server回复的dhcp offer数据包判断该网口是否应该配置为wan口。可直接在串口键入指令”send + 端口名称”实现往该端口发包,例如send eth0.2。若收到回复,则会在串口返回log并将端口名称写入/tmp/dhcp_iface文件,供脚本读取并配置。
收到回复时会返回:
ifindex : 8 receive dhcp offer in ifindex 8, start setting
无法收到回复则会阻塞等待,直到auto_adapt.sh将其关闭。
利用以上指令,可以在auto_adapt.sh脚本里通过读取/tmp/dhcp_iface文件判断某个端口是否应该配置成wan口。
// send.c 代码 #include<stdio.h> #include <unistd.h> #include<stdlib.h> #include<errno.h> #include<string.h> #include<sys/socket.h> #include <netinet/if_ether.h> #include <netinet/ip.h> #include <linux/udp.h> #include <net/if.h> #include <linux/sockios.h> #include <linux/filter.h> #include <linux/if_packet.h> #include <sys/ioctl.h> #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) #define CONFIG_UDHCPC_SLACK_FOR_BUGGY_SERVERS 80 #define ENABLE_UDHCPC_SLACK_FOR_BUGGY_SERVERS 1 #define MAC_BCAST_ADDR (uint8_t *) "\xff\xff\xff\xff\xff\xff" #define BB_LITTLE_ENDIAN 0 #define DHCP_OPTIONS_BUFSIZE 308 #define BOOTREQUEST 1 #define DHCPOFFER 2 #define DHCP_MAGIC 0x63825363 #define DHCP_END 0xff #define IF_FEATURE_UDHCP_PORT(...) #define OPT_CODE 0 #define OPT_LEN 1 #define OPT_DATA 2 #define DHCP_PADDING 0x00 #define DHCP_CLIENT_ID 0x3d #define DHCP_OPTION_OVERLOAD 0x34 #define DHCP_MESSAGE_TYPE 0x35 #define DHCP_END 0xff #define LISTEN_RAW 2 #define PACKET_AUXDATA 8 #define FILE_FIELD 1 #define SNAME_FIELD 2 struct dhcp_packet { uint8_t op; /* BOOTREQUEST or BOOTREPLY */ uint8_t htype; /* hardware address type. 1 = 10mb ethernet */ uint8_t hlen; /* hardware address length */ uint8_t hops; /* used by relay agents only */ uint32_t xid; /* unique id */ uint16_t secs; /* elapsed since client began acquisition/renewal */ uint16_t flags; /* only one flag so far: */ #define BROADCAST_FLAG 0x8000 /* "I need broadcast replies" */ uint32_t ciaddr; /* client IP (if client is in BOUND, RENEW or REBINDING state) */ uint32_t yiaddr; /* 'your' (client) IP address */ /* IP address of next server to use in bootstrap, returned in DHCPOFFER, DHCPACK by server */ uint32_t siaddr_nip; uint32_t gateway_nip; /* relay agent IP address */ uint8_t chaddr[16]; /* link-layer client hardware address (MAC) */ uint8_t sname[64]; /* server host name (ASCIZ) */ uint8_t file[128]; /* boot file name (ASCIZ) */ uint32_t cookie; /* fixed first four option bytes (99,130,83,99 dec) */ uint8_t options[388]; } ; struct ip_udp_dhcp_packet { struct iphdr ip; struct udphdr udp; struct dhcp_packet data; } ; struct udp_dhcp_packet { struct udphdr udp; struct dhcp_packet data; } ; enum { IP_UDP_DHCP_SIZE = sizeof(struct ip_udp_dhcp_packet) - CONFIG_UDHCPC_SLACK_FOR_BUGGY_SERVERS, UDP_DHCP_SIZE = sizeof(struct udp_dhcp_packet) - CONFIG_UDHCPC_SLACK_FOR_BUGGY_SERVERS, DHCP_SIZE = sizeof(struct dhcp_packet) - CONFIG_UDHCPC_SLACK_FOR_BUGGY_SERVERS, }; uint16_t inet_cksum(uint16_t *addr, int nleft) { unsigned sum = 0; while (nleft > 1) { sum += *addr++; nleft -= 2; } if (nleft == 1) { if (BB_LITTLE_ENDIAN) sum += *(uint8_t*)addr; else sum += *(uint8_t*)addr << 8; } sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */ sum += (sum >> 16); /* add carry */ return (uint16_t)~sum; } int udhcp_end_option(uint8_t *optionptr) { int i = 0; while (optionptr[i] != DHCP_END) { if (optionptr[i] != DHCP_PADDING) i += optionptr[i + OPT_LEN] + OPT_DATA-1; i++; } return i; } int udhcp_read_interface(const char *interface, int *ifindex, uint8_t *mac) { /* char buffer instead of bona-fide struct avoids aliasing warning */ char ifr_buf[sizeof(struct ifreq)]; struct ifreq *const ifr = (void *)ifr_buf; int fd; memset(ifr, 0, sizeof(*ifr)); fd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); ifr->ifr_addr.sa_family = AF_INET; strncpy(ifr->ifr_name, interface,16); if (ifindex) { if (ioctl(fd, SIOCGIFINDEX, ifr) != 0) { close(fd); return -1; } *ifindex = ifr->ifr_ifindex; } if (mac) { if (ioctl(fd, SIOCGIFHWADDR, ifr) != 0) { close(fd); return -1; } memcpy(mac, ifr->ifr_hwaddr.sa_data, 6); } close(fd); return 0; } int send_packet(struct dhcp_packet *dhcp_pkt,int ifindex) { int fd; struct sockaddr_ll dest_sll; struct ip_udp_dhcp_packet packet; int result = -1; unsigned padding; fd=socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_IP)); if(fd==-1){ printf("socket failed\n"); } memset(&dest_sll, 0, sizeof(dest_sll)); memset(&packet, 0, offsetof(struct ip_udp_dhcp_packet, data)); packet.data = *dhcp_pkt; dest_sll.sll_family = AF_PACKET; dest_sll.sll_protocol = htons(ETH_P_IP); dest_sll.sll_ifindex = ifindex; dest_sll.sll_halen = 6; const uint8_t *dest_arp = MAC_BCAST_ADDR; memcpy(dest_sll.sll_addr, dest_arp, 6); bind(fd, (struct sockaddr *)&dest_sll, sizeof(dest_sll)); padding = DHCP_OPTIONS_BUFSIZE - 1 - udhcp_end_option(packet.data.options); if (padding > DHCP_SIZE - 300) padding = DHCP_SIZE - 300; packet.ip.protocol = IPPROTO_UDP; packet.ip.saddr = INADDR_ANY; packet.ip.daddr = INADDR_BROADCAST; packet.udp.source = htons(68); packet.udp.dest = htons(67); packet.udp.len = htons(UDP_DHCP_SIZE - padding); packet.ip.tot_len = packet.udp.len; packet.udp.check = inet_cksum((uint16_t *)&packet,IP_UDP_DHCP_SIZE - padding); packet.ip.tot_len = htons(IP_UDP_DHCP_SIZE - padding); packet.ip.ihl = sizeof(packet.ip) >> 2; packet.ip.version = IPVERSION; packet.ip.ttl = IPDEFTTL; packet.ip.check = inet_cksum((uint16_t *)&packet.ip, sizeof(packet.ip)); result = sendto(fd, &packet, IP_UDP_DHCP_SIZE - padding, /*flags:*/ 0,(struct sockaddr *) &dest_sll, sizeof(dest_sll)); // close(fd); return result; } void udhcp_init_header(struct dhcp_packet *packet) { memset(packet, 0, sizeof(*packet)); packet->op = BOOTREQUEST; packet->htype = 1; /* ethernet */ packet->hlen = 6; packet->cookie = htonl(DHCP_MAGIC); uint8_t const_options[388] = {0x35,0x01,0x01,0x39,0x02,0x02,0x40,0x37,0x08,0x01,0x03,0x06,0x0c,0x0f,0x1c,0x2a, 0x79,0x3c,0x0c,0x75,0x64,0x68,0x63,0x70,0x20,0x31,0x2e,0x32,0x39,0x2e,0x33,0x0c,0x07, 0x4f,0x70,0x65,0x6e,0x57,0x72,0x74,0xff}; memcpy(packet->options, const_options, 41); } static void init_packet(struct dhcp_packet *packet, uint8_t *mac) { udhcp_init_header(packet); packet->xid = rand(); packet->secs = htons(0); memcpy(packet->chaddr, mac, 6); } static int sockfd = -1; int setsockopt_int(int fd, int level, int optname, int optval) { return setsockopt(fd, level, optname, &optval, sizeof(int)); } static int udhcp_raw_socket(int ifindex) { int fd; struct sockaddr_ll sock; fd = socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_IP)); memset(&sock, 0, sizeof(sock)); /* let's be deterministic */ sock.sll_family = AF_PACKET; sock.sll_protocol = htons(ETH_P_IP); sock.sll_ifindex = ifindex; bind(fd, (struct sockaddr *) &sock, sizeof(sock)); setsockopt_int(fd, SOL_PACKET, PACKET_AUXDATA,1); return fd; } static int udhcp_recv_raw_packet(struct dhcp_packet *dhcp_pkt, int fd) { int bytes; struct ip_udp_dhcp_packet packet; uint16_t check; unsigned char cmsgbuf[CMSG_LEN(sizeof(struct tpacket_auxdata))]; struct iovec iov; struct msghdr msg; struct cmsghdr *cmsg; iov.iov_base = &packet; iov.iov_len = sizeof(packet); memset(&msg, 0, sizeof(msg)); msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = cmsgbuf; msg.msg_controllen = sizeof(cmsgbuf); for (;;) { bytes = recvmsg(fd, &msg, 0); if (bytes < 0) { if (errno == EINTR) continue; printf("packet read error, ignoring\n"); return bytes; /* returns -1 */ } break; } if (bytes < (int) (sizeof(packet.ip) + sizeof(packet.udp))) { printf("packet is too short, ignoring\n"); return -2; } if (bytes < ntohs(packet.ip.tot_len)) { printf("oversized packet, ignoring\n"); return -2; } bytes = ntohs(packet.ip.tot_len); if (packet.ip.protocol != IPPROTO_UDP || packet.ip.version != IPVERSION || packet.ip.ihl != (sizeof(packet.ip) >> 2) || packet.udp.dest != htons(68) || ntohs(packet.udp.len) != (uint16_t)(bytes - sizeof(packet.ip)) ) { printf("unrelated/bogus packet, ignoring\n"); return -2; } /* verify IP checksum */ check = packet.ip.check; packet.ip.check = 0; if (check != inet_cksum((uint16_t *)&packet.ip, sizeof(packet.ip))) { printf("bad IP header checksum, ignoring\n"); return -2; } for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { if (cmsg->cmsg_level == SOL_PACKET && cmsg->cmsg_type == PACKET_AUXDATA ) { struct tpacket_auxdata *aux = (void *)CMSG_DATA(cmsg); if (aux->tp_status & TP_STATUS_CSUMNOTREADY) goto skip_udp_sum_check; } } memset(&packet.ip, 0, offsetof(struct iphdr, protocol)); packet.ip.tot_len = packet.udp.len; /* yes, this is needed */ check = packet.udp.check; packet.udp.check = 0; if (check && check != inet_cksum((uint16_t *)&packet, bytes)) { printf("packet with bad UDP checksum received, ignoring\n"); return -2; } skip_udp_sum_check: if (packet.data.cookie != htonl(DHCP_MAGIC)) { printf("packet with bad magic, ignoring\n"); return -2; } bytes -= sizeof(packet.ip) + sizeof(packet.udp); memcpy(dhcp_pkt, &packet.data, bytes); return bytes; } uint8_t* udhcp_get_option(struct dhcp_packet *packet, int code) { uint8_t *optionptr; int len; int rem; int overload = 0; enum { FILE_FIELD101 = FILE_FIELD * 0x101, SNAME_FIELD101 = SNAME_FIELD * 0x101, }; optionptr = packet->options; rem = sizeof(packet->options); while (1) { if (rem <= 0) { complain: printf("bad packet, malformed option field\n"); return NULL; } if (optionptr[OPT_CODE] == DHCP_PADDING) { rem--; optionptr++; continue; } if (optionptr[OPT_CODE] == DHCP_END) { if ((overload & FILE_FIELD101) == FILE_FIELD) { overload |= FILE_FIELD101; /* "we looked at it" */ optionptr = packet->file; rem = sizeof(packet->file); continue; } if ((overload & SNAME_FIELD101) == SNAME_FIELD) { overload |= SNAME_FIELD101; /* "we looked at it" */ optionptr = packet->sname; rem = sizeof(packet->sname); continue; } break; } if (rem <= OPT_LEN) goto complain; /* complain and return NULL */ len = 2 + optionptr[OPT_LEN]; rem -= len; if (rem < 0) goto complain; /* complain and return NULL */ if (optionptr[OPT_CODE] == code) { return optionptr + OPT_DATA; } if (optionptr[OPT_CODE] == DHCP_OPTION_OVERLOAD) { if (len >= 3) overload |= optionptr[OPT_DATA]; } optionptr += len; } return NULL; } int main(int argc, char* argv[]){ int result; char *iface; uint8_t client_mac[6]; iface = (char *)malloc(sizeof(char)); iface = argv[1]; int ifindex; udhcp_read_interface(iface,&ifindex,client_mac); if(ifindex) printf("ifindex : %d\n",ifindex); else { return 0; } sockfd = udhcp_raw_socket(ifindex); struct dhcp_packet packet; init_packet(&packet, client_mac); result = send_packet(&packet,ifindex); udhcp_recv_raw_packet(&packet, sockfd); uint8_t *message; message = udhcp_get_option(&packet, DHCP_MESSAGE_TYPE); if (*message == DHCPOFFER) { char cmd[128]; sprintf(cmd,"echo '%s' > /tmp/dhcp_iface",iface); system(cmd); printf("receive dhcp offer in ifindex %d, start setting\n",ifindex); } else printf("no dhcp offer received\n"); return result; }
3.3.2 auto_adapt.sh
实现wan/lan自适应的核心脚本,被编译到/bin/auto_adapt.sh路径下,用法为:
auto_adapt.sh start 立即进行自动配置
auto_adapt.sh set 2 指定将2号端口配置号为wan口
主要函数解析:
start() { init() --初始化,备份network配置并划分vlan checkport() --调用send,发送数据包寻找wan口 kill_all() --1s后停止send,kill所有send进程。 dinit() --寻找到wan口后恢复network配置 set_wan() --根据结果配置wan口。直接调用该函数可完成手动配置。 }
#!/bin/sh default_ports_lan=`uci get network.@switch_vlan[0].ports` default_ports_wan=`uci get network.@switch_vlan[1].ports` default_ports=`uci get network.@switch_vlan[1].ports | awk -F ' ' '{print $2}'` port_str=${default_ports_lan/$default_ports/$default_ports_wan} set_wan() { if [ $1 -ne 0 ]; then port_n=`expr $1 - 1`' ' port_lan=${port_str/$port_n/''} port_wan=${port_n}${default_ports} fi uci set network.@switch_vlan[0].ports="$port_lan" uci set network.@switch_vlan[1].ports="$port_wan" uci commit } dinit() { cp /tmp/adapt_network /etc/config/network rm /tmp/adapt_network } kill_all() { pid_fd=`ps | grep "send" |grep "eth0." | grep -v "grep" | awk -F ' ' '{print $1}'` kill $pid_fd } checkport() { a=0 rv=0 for i in `seq 1 5` do { send eth0."$i" & }& done wait sleep 1 kill_all } init() { cp /etc/config/network /tmp/adapt_network echo " " > /etc/config/network for i in `seq 1 5` do uci set network.wan"$i"='interface' uci set network.wan"$i".ifname=eth0."$i" uci set network.wan"$i".proto=dhcp done a=`uci add network switch` uci set network.$a.name="switch0" uci set network.$a.reset="1" uci set network.$a.enable_vlan="1" for i in `seq 1 5` do a=`uci add network switch_vlan` uci set network.$a.device="switch0" uci set network.$a.vlan="$i" uci set network.$a.ports="`expr $i - 1` $default_ports" done uci commit /etc/init.d/network reload } start() { a=0 init rm /tmp/dhcp_iface for i in `seq 1 3` do checkport if [ -f "/tmp/dhcp_iface" ]; then a=`cat /tmp/dhcp_iface | awk -F '.' '{print $2}'` rm /tmp/dhcp_iface break fi sleep 1 done dinit if [ "$a" -ne 0 ];then set_wan $a fi /etc/init.d/network reload & } case $1 in start) start ;; set) set_wan $2 /etc/init.d/network reload ;; *) echo "commond error" ;; esac
3.3.3 auto_adapt.htm
用于显示其功能的htm文件,配合feeds/luci/modules/luci-mod-network/luasrc/controller/admin/network文件,将自适应功能以网页+接口调用的形式可视化。开发流程可参考SiWiFi接口开发手册。最终界面如下所示:
手动配置一栏显示当前端口的连接状态,点击”lan”按钮即可将对应端口配置为wan口。
自动配置一栏点击立即配置后则立即进行一次wan口自动配置。若未找到wan口则配置不变。
开机自启动一栏可选择是否启用。启用后,开机时会进行一次wan口自动配置。
3.3.4 rc.local
开机自动运行的脚本,读取/etc/config/network文件lan下的auto_adapt选项。若该选项被配置为1,则立即进行一次自动配置。
4 测试用例
4.1 测试环境配置
一台待已编译自启动脚本的待测路由、串口、能获取到ip并上网的网线。
4.2 测试流程
4.2.1 手动配置测试
1、将网线插入第1号口。
2、串口执行auto_adapt.sh set 1,或者浏览器访问路由器配置界面,打开“网络”选项下的“wan/lan自适应”,点击手动配置下的第一个按钮。
3、等待约10s,同时在串口观察是否打印log。
4、在串口输入ping www.baidu.com检测是否能上网。更换端口,重复测试。
4.2.2 自动配置测试
1、将网线插入路由器任意端口。
2、串口执行auto_adapt.sh start,或者浏览器访问路由器配置界面,打开“网络”选项下的“wan/lan自适应”,点击自动配置下的“立即配置”按钮。
3、等待约10s,同时在串口观察是否打印log。
4、在串口输入ping www.baidu.com检测是否能上网。更换端口,重复测试。
4.2.3 开机自启动测试
1、将外部网线插入路由器任意端口。
2、在串口输入指令:uci set network.lan.auto_adapt=1;uci commit network。或者浏览器访问路由器配置界面,打开“网络”选项下的“wan/lan自适应”,启用“开机自启动”选项下的按钮。
3、重启路由器。待路由器重启完成。
4、在串口输入ping www.baidu.com检测是否能上网。更换端口,重复测试。
4.3 测试结果
以上所有操作执行后,均会有自适应脚本的相应log打印;等待一段时间后,路由器能正常上网,说明功能正常。
5 FAQ
Q: 脚本在开机后没有自启动,如何检查?
A:在/etc/rc.local中,运行脚本指令之前添加log,确认自启动脚本是否被调用。若此log未出现,说明rc.local有问题而非自启动脚本本身的问题。之后在auto_adapt.sh脚本中添加log,并用ps指令查看脚本是否已在后台运行。若log已打但脚本未运行,则可判断是脚本逻辑问题导致运行结束。
Q:配置后路由器无法上网,如何检查?
A:首先查看/etc/config/network文件是否按照规范配置,wan口有没有划分正确;串口输入/etc/init.d/network restart重启网络,差看是否是因为配置未生效;查看是否是外部网络不通畅的问题。
Q:配置之后网页无法点击?
A:这是因为更换了wan/lan配置并重启了网络,刷新网页或重新登录即可。
项目的代码地址:https://github.com/Siflower/1806_SDK/tree/release/openwrt-18.06/package/network/services/auto_adapt