您的位置:首页 > 运维架构

OpenWrt的学习和总结

2016-02-18 17:03 302 查看
OpenWrt的学习和总结

内容目录

1OpenWrt背景知识
2
2OpenWrt
基础知识 2
2.1目录结构
2
2.2扩展软件包feeds
3
2.3OpenWrt SDK 4
2.4固件升级
8
3OpenWrt内部机制
8
3.1UCI(unifiedconfiguration
Interface) 8
3.2ubus 11
4LuCI 15
5参考
15

1OpenWrt背景知识

Linksys成立于1988年,2003年被思科收购,最著名的产品为WRT54G,为降低成本Linksys决定使用基于Linux操作系统的固件,由于版权原因,释放WRT54G/GS的源码,网上出现了很多不同版本的Firmware。流行的第三方路由固件,主要DDWrt,Tomato,OpenWrt三个。
DDWRT:界面美观,设置简单直观,易用性比较好,官方固件自带的功能是三大固件中最为丰富的,对新路由的支持也十分迅速,可支持迅速的同时,由于新代码的加入,导致DDWRT经常出现一些Bug,一些可能之前已经解决的Bug,往往又会在之后的某次更新中原地满状态复活,着实让人喷血,最让人呕病的,是孱弱的QOS功能,DDWRT的QOS功能只能说聊胜于无吧。
Tomato:界面尚可,设置也算简单,易用性还行,流量的统计查看等功能是三个固件中做的最好的.官版Tomato是所有第三方路由固件中最为稳定的,它的稳定源自于它的保守,官方版本的Tomato好几个版本之前就已经没有再加入什么新功能,基本是对原有软件的升级和除Bug,让本来就稳定无比的Tomato更加稳定,可惜成也萧何败也萧何,Tomato的声誉来自于它的稳定,源自它的保守,它的缺点也来自于它的保守,由于这三大固件都源于思科当初开放的路由源码,所以对思科以及和思科一样采用BCM的54M芯片的机子的支持程度是最好的。
Openwrt:从零开始,一点一点的把各软件加入进去,使其接近Linksys版Firmware的功能。是三大固件中扩展性最好的固件,通过扩展,可以实现很多超过路由本职工作的事情,比如脱机下载,网站,论坛,QOS功能也十分强大,不逊色于Tomato的QOS,设置得当后网络表现会让人偷着乐,而且OpenWrt对新路由的支持也是十分迅速,被认为是最有前途的固件,因为可以在新路由强劲CPU的支持下获得很好的性能表现,各种功能的软件扩展包比比皆是。
OpenWrt可以被描述为一个嵌入式的Linux发行版,而不是试图建立一个单一的、静态的系统。OpenWrt的包管理提供了一个完全可写的文件系统。对于开发人员,OpenWrt是使用框架来构建应用程序,而无需建立一个完整的固件来支持;对于用户来说,这意味着其拥有完全定制的能力,可以用前所未有的方式使用该设备。

2OpenWrt
基础知识

关于Openwrt的源代码下载,途径有二,一是通过svn,一是通过git,建议使用svn,因为Openwrt主要以svn来维护Openwrt系统的版本。另外,请注意Openwrt中不同的分支版本,一个是用得较多的开发快照,俗称trunk,二是稳定版,俗称backfire。

2.1目录结构

有几个重要目录:package,target,
build_dir, staging_dir, bin, dl
上图是openwrt的基本的目录结构,其中白色部分是直接下载源码就有的源目录,蓝色部分则是通过make之后产生的目录。

tool和toolchain:是编译固件image,获取内核头文件,二进制编译器和调试器,c库文件,需要用到的通用工具。
include:存放*.mk文件,这里的文件是在Makefile里被include的
scripts:对openwrt的包进行管理的perl脚本,存放各类脚本的目录。比如:feeds脚本
target:里面是各个平台(arch)的相关代码
package:里面包含了我们在配置文件里设定的所有编译好的软件包。默认情况下,会有默认选择的软件包。
bin:用来存放生成的ipk包,固件。
build_dir:目录编译时的临时目录,进行包的解压,编译和打补丁等。
staging_dir:是工具链的安装位置
dl:是'download'的缩写,在编译前期,需要从网络下载的数据包都会放在这个目录下,这些软件包的一个特点就是,会自动安装在所编译的固件中,也就是我们makemenuconfig的时候,为固件配置的一些软件包。如果我们需要更改这些源码包,只需要将更改好的源码包打包成相同的名字放在这个目录下,然后开始编译即可。编译时,会将软件包解压到build_dir目录下。
feeds:是通过命令./scripts/feedsupdate
-a; ./scripts/feeds install-a之后安装的扩展包目录,这个目录将所有的文件链接到package/feeds/中去了,里面存放的就是按照feeds.conf.default文件中列举的要处理的文件,所展开得到的目录。目录中存放的东西,和package目录中大致相似,指导如何下载和编译对应模块的。

2.2扩展软件包feeds

如果缺少一些应用程序可以是用feeds管理工具来安装,feeds即为包含到你的OpenWrt环境中的额外软件包,有一个用perl写的脚本(feeds)放在scripts目录下,会根据feeds.config.default文件来进行相关的操作,feeds命令有:

./scripts/feedsupdate
./scripts/feedslist
./scripts/feedsinstall
./scripts/feedsuninstall
./scripts/feedssearch
./scripts/feessclean

在feed.config.default文件中有:
src-gitpackages https://github.com/openwrt/packages.git;for-15.05
src-gitluci https://github.com/openwrt/luci.git;for-15.05 src-linkluci ../qca/feeds/luci
src-svnxwrt http://x-wrt.googlecode.com/svn/trunk/package
src-svnphone svn://svn.openwrt.org/openwrt/feeds/phone
.....
.....

一般情况,你至少需要含packagesfeeds,其他可根据需求下载、安装feeds。
*packages
提供众多库、工具等基本功能;也是其他feed所依赖的软件源,因此在安装其他feed前一定要先安装packages!
*luci OpenWrt默认的GUI(WEB管理界面)

比如先安装luci先要安装packages包:
$./scripts/feeds install -p packages -a
$./scripts/feeds install -p luci -a

举例:
下载、更新完feeds:
$./scripts/feeds update -a

要安装feeds.conf中定义的全部feed,
$./scripts/feeds install -a

安装某个feed的全部,
$./scripts/feeds install -p luci -a

搜索安装所需的软件包,这时候可以搜索安装相关的软件包,例如安装和蓝牙有关的软件包:
$./scripts/feeds search bluetooth
Searchresults in feed 'packages':
anyremote A bluetooth remote control app
bemusedlinuxserver Bemused linux server
bluez-hcidump Bluetooth packet analyzer
bluez-libs Bluetooth library
bluez-utils Bluetooth utilities
miax A console iax (asterisk) client
python-bluez Python wrapper for the BlueZ Bluetooth stack

比如需要安装bluez-libs和bluez-utils这两个包,可以直接安装他们:
$./scripts/feeds install bluez-libs bluez-utils
Collectingpackage info: done
Collectingtarget info: done
Installingpackage 'bluez-libs'
Installingpackage 'gettext'
Installingpackage 'libiconv'
Installingpackage 'bluez-utils'
Installingpackage 'dbus'
Installingpackage 'expat'
Installingpackage 'gettext-full'
Installingpackage 'libiconv-full'
feeds已经相当智能了,能够根据软件包间的依赖关系,自动把所依赖的软件包也一同安装了

2.3OpenWrt SDK

如果要编译应用程序可以在OpenWrt源码目录下,也可以使用OpenWrtSDK。在makemenuconfig
是选择
[*]Build the OpenWrtSDK,在bin目录下生成,OpenWrt-SDK-ar71xx-xxxxx-0.9.33.2.tar.bz2包,目录结构与OpenWrt结构基本相同,应用程序在SDK下编译就可以,里面有需要的所有依赖库和交叉编译工具。

新建一个helloworldproject举例:
OpenWrt-SDK/package$mkdir helloworld
OpenWrt-SDK/package/helloworld$ls
Makefile src
OpenWrt-SDK/package/helloworld$ls src
helloworld.c
Makefile

橙色的Makefile为OpenWrt的Makefile,下面是模板:
##############################################

#OpenWrt Makefile for helloworld program

#

#

#Most of the variables used here are defined in

#the include directives below. We just need to

#specify a basic description of the package,

#where to build our program, where to find

#the source files, and where to install the

#compiled program on the router.

#

#Be very careful of spacing in this file.

#Indents should be tabs, not spaces, and

#there should be no trailing whitespace in

#lines that are not commented.

#

##############################################

include$(TOPDIR)/rules.mk

#Name and release number of this package

PKG_NAME:=helloworld

PKG_RELEASE:=1

#This specifies the directory where we're going to
build the program.

#The root build directory, $(BUILD_DIR), is by default
thebuild_mipsel

#directory in your OpenWrt SDK directory

PKG_BUILD_DIR:= $(BUILD_DIR)/$(PKG_NAME)

include$(INCLUDE_DIR)/package.mk

#Specify package information for this program.

#The variables defined here should be self explanatory.

#If you are running Kamikaze, delete the DESCRIPTION

#variable below and uncomment the Kamikaze define

#directive for the description below

definePackage/helloworld

SECTION:=utils

CATEGORY:=Utilities

TITLE:=Helloworld-- prints a snarky message

endef

#Uncomment portion below for Kamikaze and delete DESCRIPTION
variableabove

definePackage/helloworld/description

Ifyou can't figure out what this program does, you're
probably

brain-deadand need immediate medical attention.

endef

#Specify what needs to be done to prepare for building
the package.

#In our case, we need to copy the source files to
the build directory.

#This is NOT the default. The default uses the PKG_SOURCE_URL
and the

#PKG_SOURCE which is not defined here to download
the source from theweb.

#In order to just build a simple program that we have
just written, itis

#much easier to do it this way.

defineBuild/Prepare

mkdir-p $(PKG_BUILD_DIR)

$(CP)./src/* $(PKG_BUILD_DIR)/

endef

#We do not need to define Build/Configure or Build/Compile
directives

#The defaults are appropriate for compiling a simple
program such asthis one

#Specify where and how to install the program. Since
we only have onefile,

#the helloworld executable, install it by copying
it to the /bindirectory on

#the router. The $(1) variable represents the root
directory on therouter running

#OpenWrt. The $(INSTALL_DIR) variable contains a command
to preparethe install

#directory if it does not already exist. Likewise
$(INSTALL_BIN)contains the

#command to copy the binary file from its current
location (in ourcase the build

#directory) to the install directory.

definePackage/helloworld/install

$(INSTALL_DIR)$(1)/bin

$(INSTALL_BIN)$(PKG_BUILD_DIR)/helloworld $(1)/bin/

endef

#This line executes the necessary commands to compile
our program.

#The above define directives specify all the information
needed, butthis

#line calls BuildPackage which in turn actually uses
this informationto

#build a package.

$(eval$(call BuildPackage,helloworld))

src目录下的Makefile为:

#build helloworld executable when user executes "make"

helloworld:helloworld.o

$(CC)$(LDFLAGS) helloworld.o -o helloworld

helloworld.o:helloworld.c

$(CC)$(CFLAGS) -c helloworld.c

#remove object files and executable when user executes
"makeclean"

clean:

rm*.o helloworld

编译命令为:
OpenWrt-SDK$makeV=99 package/helloworld/compile
成功后会在bin目录下生成helloworld_1_ar71xx.ipk,在板子上是用opkg命令安装。
opkginstall helloworld_1_ar71xx.ipk
就可以安装到板子上。

Opkg是一个轻量快速的套件管理系统,目前已成为Opensource界嵌入式系统标准。常用于路由、交换机等嵌入式设备中,用来管理软件包的安装升级与下载。

常用命令
opkgupdate
更新可以获取的软件包列表
opkgupgrade
对已经安装的软件包升级
opkglist
获取软件列表
opkginstall
安装指定的软件包
opkgremove
卸载已经安装的指定的软件包

2.4固件升级

Thegeneric Flash layout is:

Layer0
rawflash
Layer1
bootloader

partition(s)
optional

SoC

specific

partition(s)
OpenWrtfirmware partition
optional

SoC

specific

partition(s)
Layer2
LinuxKernel
rootfs

mounted:"/",OverlayFSwith/overlay
Layer3
/dev/root

mounted: "/rom",SquashFS

sizedepends on selected packages
rootfs_data

mounted:"/overlay",JFFS2

"free"space
/overlay与/rom共同构成统一的/根目录,rootfs_data使用overlayFS,可读写。
源码编译后会在bin目录下生成固件包,对应上图Layer1中的”OpenWrtfirmware
partition“
openwrt-ar71xx-generic-ap152-16M-squashfs-sysupgrade.bin
三种方式:网页升级,uboot,进入系统使用sysupgrade命令。

3OpenWrt内部机制

3.1UCI(unifiedconfiguration
Interface)

Linux下的各种软件包有不同的配置脚本,每个脚本的语法格式和操作方式不同,这样的设计虽然体现了各个软件自身的优势,但同时也增加了学习难度,所以OpenWrt引入了一套配置参数管理系统,就是UCI(unifiedconfiguration
Interface),意在OpenWrt整个系统的配置集中化。
UCI是一个用C语言编写的小应用程序(也可用作一个shell脚本封装器),其开发目的是集中管理一个OpenWrt设备内的所有配置选项。UCI是对OpenWrt历史版本WhiteRussian上NVRAM基础配置管理系统的一个继承,是一个对众多应用程序自带的标准配置文件的包装,比如像:/etc/network/interfaces,/etc/exports,/etc/dnsmasq.conf,/etc/samba/samba.conf等。你可以用任意文本编辑器或命令行应用程序uci来修改它们,也可通过各种编程API(如Shell,Lua和C)来修改它们,配置位置放在/etc/config下。如果要开机读取/etc/config下面的配置文件进行相应的设置,需要在/etc/init.d下面增加启动脚本。

UCI命令行工具:
uci:invalid option -- h

Usage:uci [<options>] <command> [<arguments>]

Commands:

batch

export [<config>]

import [<config>]

changes [<config>]

commit [<config>]

add <config> <section-type>

add_list <config>.<section>.<option>=<string>

del_list <config>.<section>.<option>=<string>

show [<config>[.<section>[.<option>]]]

get <config>.<section>[.<option>]

set <config>.<section>[.<option>]=<value>

delete <config>[.<section>[[.<option>][=<id>]]]

rename <config>.<section>[.<option>]=<name>

revert <config>[.<section>[.<option>]]

reorder <config>.<section>=<position>

UCI的依赖项
libuci一个用C语言写的UCI的小程序库
libuci-lua
一个Lua语言的插件,可被像LuCI的程序使用.

举例子:/etc/config/network

root@OpenWrt:/#cat /etc/config/network

configinterface 'loopback'

optionifname 'lo'

optionproto 'static'

optionipaddr '127.0.0.1'

optionnetmask '255.0.0.0'

configglobals 'globals'

optionula_prefix 'auto'

configinterface 'wan0'

optionifname 'eth1'

optionproto 'dhcp'

configinterface 'lan0'

optionifname 'eth0_1 eth0_2 eth0_3 eth0_4'

optiontype 'bridge'

configinterface 'lan0_zone0'

optionifname 'br-lan0'

optionproto 'static'

optionipaddr '192.168.1.1'

optionnetmask '255.255.255.0'

root@OpenWrt:/#uci show network

network.loopback=interface

network.loopback.ifname='lo'

network.loopback.proto='static'

network.loopback.ipaddr='127.0.0.1'

network.loopback.netmask='255.0.0.0'

network.globals=globals

network.globals.ula_prefix='auto'

network.wan0=interface

network.wan0.ifname='eth1'

network.wan0.proto='dhcp'

network.lan0=interface

network.lan0.ifname='eth0_1eth0_2 eth0_3 eth0_4'

network.lan0.type='bridge'

network.lan0_zone0=interface

network.lan0_zone0.ifname='br-lan0'

network.lan0_zone0.proto='static'

network.lan0_zone0.ipaddr='192.168.1.1'

network.lan0_zone0.netmask='255.255.255.0'

api函数:
uci_lookup_ptr()
uci_foreach_element()
uci_to_section()
uci_lookup_option_string()
uci_set()
uci_commit()
...
...

如果你想在OpenWrt之外使用libuci(例如:您正在计算机上用C语言开发一个应用程序),下载源码生成库,编译连接
$(CC)test.o -o test -luci

注意:所有的uciset,uci
add,uci rename和ucidelete命令都是在/tmp中实现,并用ucicommit命令立即写入flash.

3.2ubus

ubus是为了OpenWrt中守护进程和应用程序之间通讯开发的,类似桌面的DBus,设计理念上与DBus基本保持一致,区别是简化的API和简练的模型,以适应embeddedrouter的特殊环境。与DBus一样也是使用socket实现。
核心部分是ubusd守护进程,它提供了其他守护进程将自己注册以及发送消息的接口。因为这个,接口通过使用Unixsocket来实现,并使用TLV(type-length-value)消息,ubus内部使用Blob_buf,Blob_attr等结构来表示。
ubus有两种调用,一个是method调用,一个是notification,其中method包括等待函数返回和不用等待返回,notification是广播和DBus的signal类似。ubus使用是先建立连接,然后把连接加入epollset中。下面是它的一些调用API。

uloop_init();
创建epoll句柄,最多监听32个fd
ubus_connect();
创建ubus连接
ubus_add_uloop();
把创建的ubus连接注册到epoll中。
ubus_add_object();
注册对象到的ubus连接。
uloop_run();
等待I/O事件发生,调用相对应的对象的功能函数。
ubus_free();
关闭ubus连接
uloop_done();
关闭epoll句柄

ubus调试有一个命令行工具叫ubus,ubus可以和ubusd服务器交互(和当前所有已经注册的服务).它对研究和调试注册的命名空间以及编写脚本非常有用。可以调用带参数和返回信息的方法,它使用友好的JSON格式。JSON(JavaScriptObjects
Notation)是一种轻量级的数据交换方式,JSON是humannice
type,有两种数据结构对象和数组。
对象:
{“firstname”:“Brett”,“lastname”:“Mical”}
数组:[“aaa”,“bbb”,
“ccc”]

下面是它的命令说明。
ubus Commands:

-list [<path>] List objects

-call <path> <method> [<message>] Call an objectmethod

-listen [<path>...] Listen for events

-send <type> [<message>] Send an event

-wait_for <object> [<object>...] Wait for multipleobjects to appear on

ubuslist缺省列出所有向服务器注册的命名空间,如果调用时包含参数-v,将会显示指定命名空间更多方法参数等信息。
ubuscall
调用指定命名空间中指定的方法,并且通过消息传递给它,消息参数必须是有效的JSON字符串,并且携带函数所要求的键及值。
Ubuslisten
设置一个监听socket并观察进入的事件。
Ubussend
发送一个事件提醒。
root@uplink:~#ubus listen &

举例:
server.c
structblobmsg_policy discovery_pair_device_policy[] = {
[0]= { .name = "mac", .type = BLOBMSG_TYPE_STRING },
};
intdiscovery_pair_device_policy_sz = ARRAY_SIZE(discovery_pair_device_policy );
staticstruct ubus_method discovery_methods[] = {
UBUS_METHOD_NOARG("broadcast", discovery_broadcast ),
UBUS_METHOD_NOARG("get_devices", discovery_get_devices ),
UBUS_METHOD("pair_device", discovery_pair_device,discovery_pair_device_policy
),
UBUS_METHOD_NOARG("get_paired_devices", discovery_get_paired_devices ),
UBUS_METHOD("get_pairing_status", discovery_get_pairing_status,discovery_get_pairing_status_policy
),
};
staticstruct ubus_object_type discovery_object_type = UBUS_OBJECT_TYPE("controller.discovery",
discovery_methods );

staticstruct ubus_object discovery_object = {
.name= "controller.discovery",
.type= &discovery_object_type,
.methods= discovery_methods,
.n_methods= ARRAY_SIZE( discovery_methods ),
};

int main( int argc, char** argv)
{
structubus_context* ubus_context;
intret;
uloop_init();
ubus_context= ubus_connect( NULL );
if( !ubus_context )
{
fprintf(stderr, "Failed to connect to ubus\n" );
gotoout;
}
ubus_add_uloop(ubus_context );
ret= ubus_add_object( ubus_context, &discovery_object );
if( ret ) {
pr_debug("Failed to add discovery object: %s\n", ubus_strerror( ret) );
}
uloop_run();

ubus_free(ubus_context );
uloop_done();

out:
return0;
}

//=============================================================================
int
discovery_pair_device(
structubus_context* ubus_context,
structubus_object* obj,
structubus_request_data* req,
constchar* method,
structblob_attr* msg
)
//=============================================================================
{
structdevice_descriptor* node;
structblob_attr* args[1];
intstatus = 0;
charmac[AP_ID_LEN + 1];

blobmsg_parse(
discovery_pair_device_policy,
discovery_pair_device_policy_sz,
args,
blob_data(msg ),
blob_len(msg ) );

if( args[0] ) {
memset(mac, 0, sizeof ( mac ) );
strncpy(mac, blobmsg_data( args[0] ), sizeof ( mac ) - 1 );
}
else
gotoerror;

//dosomething and set status

blob_buf_init(&reply, 0 );
blobmsg_add_u32(&reply, "status", status );

ubus_send_reply(ubus_context, req, reply.head );

returnUBUS_STATUS_OK;
}

client程序
staticstruct ubus_context *ctx;
staticstruct blob_buf b;
int main(int argc, char **argv)
//=============================================================================
{
constchar *progname;
intoption_index;
intc;
charbuf[80];

progname= argv[0];

uint32_tid;
ubus_ctx= ubus_connect( NULL );
if(!ubus_ctx ) {
fprintf(stderr, "Couldn't connect to UBUS.\n");
return0;

}

if(ubus_lookup_id( ubus_ctx, "controller.discovery", &id )) {
fprintf(stderr, "Couldn't find controller.discovery.\n");
return0;
}

blob_buf_init(&b, 0 );

if( argc == 1 ) return print_usage( progname );
while( (c = getopt_long( argc, argv, "hdlp:s:", long_options,&option_index)) !=
-1 ) {
switch( c ) {
case'd':
ubus_invoke( ubus_ctx, id, "broadcast", b.head,print_json, NULL, 1000 );
return 1;
case'p':
sprintf( buf, "{ \"mac\" : \"%s\" }",optarg );
blobmsg_add_json_from_string( &b, string_tolower(buf) );
ubus_invoke( ubus_ctx, id, "pair_device", b.head,print_json, NULL, 1000 );
return 1;
default:
return print_usage( progname );
}
}

return0;

}

ubus命令执行:
root@OpenWrt:/#ubus list controller.discovery -v

'controller.discovery'@1cabe8bf

"broadcast":{}

"get_devices":{}

"pair_device":{"mac":"String"}

"get_paired_devices":{}

"get_pairing_status":{"mac":"String"}

root@OpenWrt:/#ubus call controller.discovery pair_device '{"mac":"00010203040

506"}'

{

"status":"(error)"

}

notification函数有:
ubus_notify();

ubus_notify_async();
ubus_register_subscriber();
ubus_subscribe()
ubus_unsubscribe();

4LuCI

OpenWrt利用uhttpd作为web服务器,OpenWrt使用LuCI实现客户端web页面配置功能。LuCI采用了MVC(模型-视图-控制)三层架构,同时其使用Lua脚本,在/usr/lib/lua/luci下面对应有model,view和controller目录。开发LuCI的配置界面一般不需要编辑任何的Html代码,除非想自己单独去创建网页(View层)。
资源目录放在/www目录下面,index.html指向了目录下的cgi-bin/luci。对于request处理方式,采用的是cgi,而所用的cgi程序就是luci。调用工作框架如下图所示:

Client端和server端采用cgi方式交互,uhttpd服务器的cgi方式中,fork出一个子进程,子进程利用execl替换为luci进程空间,并通过setenv环境变量的方式,传递一些固定格式的数据(如PATH_INFO)给luci。另外一些非固定格式的数据(post-data)则由父进程通过一个w_pipe写给luci的stdin,而luci的返回数据则写在stdout上,由父进程通过一个r_pipe读取。

LuCI的配置文件默认放在/etc/config目录下,如ip网关,等信息,可以使用UCI(unifiedconfiguration
Interface)对文件进行读写,有命令和api接口两种方式,但修改,删除操作后需要执行ucicommit,否则不会保存到文件中。

LuCI的安装两种方式:

a.使用OpenWrt源:
*转到OpenWrt根目录,编辑feed.config.default打开luci源。
*输入./scripts/feedsupdate
*输入./scripts/feedsinstall
-a -p luci
*输入makemenuconfig
*在”LuCI”菜单下你将找到所有的组件。
*make V=99

b.使用OpenWrt安装包版本库:
*添加一行文字到你的/etc/opkg.conf中,即将LuCI添加到版本库中:
srclucihttp://downloads.openwrt/kamikaze/8.09.2/YOUR_ARCHITECTURE/packages
*输入opkg
update
*LuCI简版,输入:opkg
install luci-light
LuCI普通版:opkg
install luci

5参考

https://wiki.openwrt.org/doc/techref/start
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: