OpenWrt的UCI系统

http://wiki.openwrt.org/doc/uci

UCI是Unified Configuration Interface的缩写,翻译成中文就是统一配置接口,用途就是为OpenWrt提供一个集中控制的接口。OpenWrt实现的这个工具,能够让你的不管是Lua还是PHP程序,或者SHELL程序或C程序,只要执行命令传输参数就能达到修改系统参数的目的,请参考本文后面的命令行实用工具

系统的配置应该简单直接,UCI的设计初衷即是这样的,它是NVRAM-based配置方法的继承者(基于NVRAM的配置方法起源于OpenWrt的White Russian系列,该版本目前不再更新,最后发布于2007年,版本号为0.9)。UCI可以视为OpenWrt系统功能设置的主要用户配置接口,通常来说这些配置与系统的功能关联性较大,想像一样我们平常所使用的路由器或嵌入式设备中的WEB界面中的那些配置项,就是路由器或嵌入式设备系统所集成了的功能。常见的例子如路由器的网络接口设置,无线参数设置,logging设置和远程登录设置等。

UCI目前已经支持有一小部分应用程序,因而对这些应用程序的控制会变得更加简单一些。这些第三方应用程序都会有自己的配置文件,不同的语法,不同的文件位置,如

/etc/network/interfaces

/etc/exports

/etc/dnsmasq.conf

或/etc/samba/samba.conf

由于UCI统一配置接口的出现,对这些第三方应用程序的配置只需要修改UCI的配置文件即可,就不必再去找不同的目录,写不同的语法了。当然,你安装的大多数第三方应用程序都没有提供UCI配置接口,很可能是因为这些应用程序本身就不需要向普通用户提供应用程序接口,配置文件是给开发者使用的,从这个角度上来看,没有提供UCI接口反而更好。因而,OpenWrt包维护人员只选定了一小部分必需的程序实现了UCI配置接口,下面有列出(Therefore, only a few selected programs which benefit from availability of a centralised configuration have been made UCI-compatible by the OpenWrt package maintainers (see the UCI configuration file list below))。

许多第三方程序是根据它自己对应于/etc/config下的UCI配置文件的选项去设置程序的原始配置文件,这样就实现了程序对UCI配置的兼容,然后执行一次/etc/init.d脚本完成一次配置。因而当你启动一个某个程序的UCI兼容的进程脚本时,该脚本应该不只是修改/etc/config下对应的UCI配置文件,同时也应该覆盖程序自己的原配置文件。比如Samba/CIFS程序,其原配置文件是在/etc/samba/smb.conf,而对应的UCI文件是/etc/config/samba,当/etc/config/samba文件被修改了之后,需要运行一次

/etc/init.d/samba start

之后UCI文件中的设置才会更新到原配置文件中去。

除此之外,应用程序的配置文件常常是存放在RAM而不是FLASH中,因为它不需要每次修改参数之后就去写非易性闪存了,而只在应用改变的时候它才会根据UCI文件去写非易性闪存(原文:In addition, the application’s configuration file is often stored in RAM instead of in flash, because it does not need to be stored in non-volatile memory and it is rewritten after every change, based on the UCI file.)。

OpenWrt的wiki里有一篇文章NotUCI Configuration列举了一些与UCI不兼容的自带程序,而其它的第三方程序,你得自己去查阅程序的说明了。

一般规则

UCI的配置文件被分割成/etc/config下的多个独立的文件,各个文件按名字含义对应系统的不同的功能配置。你可以通过文本编译器或者uci实用程序去修改这些配置文件,同时uci还提供了C语言/脚本/Lua等语言的应用程序接口,WEB配置页面例如Luci就是利用了uci所提供的API而实现对UCI配置文件的修改的。

不管你是采用文本编辑器还是通过命令行的方式修改了UCI配置文件,相应的服务或应用程序不会自动更新状态,这时你都必须调用一次/etc/init.d (re)start才能使刚刚对UCI配置文件的修改生效。许多兼容UCI的程序采用这样的方法来应用更新:在init.d脚本执行流中去修改自己程序的配置文件。具体说来,init.d脚本先去修改自己程序的原配置文件中的信息(如/etc/samba/smb.conf),之后重启一次应用程序,应用程序就会去读自己的配置文件(刚刚被init.d更新过的)再启动,这样应用程序的状态就更新了。仅仅重启应用程序,而不执行init.d脚本的话,/etc/config下的UCI配置文件是不会应用于应用程序的,新配置也就不生效了。

举个例子:

先登录到路由器的WEB页面把WiFi给禁用掉,这个时候你的手机搜索到你的路由器发送的SSID了,我这儿是NGTestRouter。

这时我们准备通过使用文本编辑器修改UCI再应用的方法来现使能WiFi,步骤如下:

编辑wireless文件把disabled这个项注释掉(也就是enable WiFi了)

#vi /etc/config/wireless

然后运行一次

#/etc/init.d/network restart

这时你的手机又可以看到NGTestRouter这个热点了!

UCI配置文件

(请参考原链接http://wiki.openwrt.org/doc/uci#configuration.files

文件语法

uci配置文件通常包含有一个或多个语句。所谓段(section),包含有一个或多个option语句,这些语句定义了实际的值。

下面是一个简单的配置文件:

复制代码
package 'example'

config 'example' 'test'
        option   'string'      'some value'
        option   'boolean'     '1'
        list     'collection'  'first item'
        list     'collection'  'second item'
复制代码
  • config ‘example’ ‘test’表示一个段的开始,其中example是段的类型,test为段的名字。段也可以没有名字,像config ‘example’,但是必须要有类型,类型指示了uci程序怎么去处理后面的option内容;
  • option ‘string’ ‘some value’和option ‘boolean’ ‘1’两个语句定义了段内的两个标识符的值,虽然它们一个是string一个是boolean,但是在语法没有任何区别。boolean后面可以跟’0′, ‘no’, ‘off’, ‘false’中的一个作为否的值,或者’1’, ‘yes’, ‘on’, ‘true’作为逻辑是的值;
  • 后面两行以list开头的语句,是为某个有多种选项值的option所定义的,在同一option中的选项值,它们应该有同样的名字,在这里的名字为collection。最张这两个值为收纳到同一个list表中,表中出现的顺序即你这里所定义的;
  • 标识符option和list是为了更易读而加上的,没有它们也是可以的;
  • 如果某个option没有但它不是必须的,那么uci处理程序会假定一个默认值;如果该option是必须的,而文件中没有定义,那么uci会报错或者显现出奇怪的结果;

语句中的标识和值可不必使用引号引起,除非你的字段值含有空格或者tab键。如果使用引号,那你可以随意使用单引号或者双引号。比如这样子:

option example value
option 'example' value
option example "value"
option "example" 'value'
option 'example' "value"

不过不能这样子(引号混用,字段中有空格但未用引号引起来):

option 'example" "value' (quotes are unbalanced)
option example some value with space (note the missing quotes around the value)

UCI的文件名和标识符(像option example value中的example即为标识符,value为option的值)可以使用a-z, 0-9和下划线_组合的任意字符串,不允许使用横杠线-,而option的值可以傅任意字符(像空格这样子的字段值需要用引号引起)。

 

命令行实用工具

修改配置的一种方法是直接去修改UCI配置文件。不过,UCI配置文件读和写操作都可以通过uci命令行实用工具来完成,因而如果你自己去写一个脚本来解析或写入UCI配置文件不是一个明智的选择,既浪费时间又不一定写得好。以下介绍如何使用uci命令行实用工具,并伴有一些实例参考:

在学习该工具前需要注意:uci会把先读到UCI文件,其中不认识的所有命令参数和注释会被删除!所以,像uhttpd这样安装地有详细注释的文件,在使用uci操作之后其中的注释就会被抹掉的。OpenWrt的默认WEB界面Luci就是采用了uci来写UCI文件

用法

执行下面命令查看

root@OpenWrt:~#uci

 

例子

设置一个值

把uhttpd的监听端口从80换成8080

root@OpenWrt:~# uci set uhttpd.main.listen_http=8080 
root@OpenWrt:~# uci commit uhttpd 
root@OpenWrt:~# /etc/init.d/uhttpd restart 
root@OpenWrt:~#

导出整体配置信息

复制代码
root@OpenWrt:~# uci export httpd 
package 'httpd' 

config 'httpd' 
    option 'port' '80' 
    option 'home' '/www' 

root@OpenWrt:~#
复制代码

显示一个给定配置的树

root@OpenWrt:~# uci show httpd 
httpd.@httpd[0]=httpd 
httpd.@httpd[0].port=80 
httpd.@httpd[0].home=/www 
root@OpenWrt:~#

显示一个option的值

root@OpenWrt:~# uci get httpd.@httpd[0].port 
80 
root@OpenWrt:~#

追加list的一个条目

uci add_list system.ntp.server='0.de.pool.ntp.org'

替换一个list

uci delete system.ntp.server 
uci add_list system.ntp.server='0.de.pool.ntp.org' 
uci add_list system.ntp.server='1.de.pool.ntp.org' 
uci add_list system.ntp.server='2.de.pool.ntp.org'

UCI路径

假设有下面的UCI文件

复制代码
# /etc/config/foo 
config bar 'first' 
    option name 'Mr. First' 
config bar 
    option name 'Mr. Second' 
config bar 'third' 
    option name 'Mr. Third'
复制代码

那么下面三组路径的执行得到的值分别各自相等

复制代码
# Mr. First 
uci get foo.@bar[0].name 
uci get foo.@bar[-0].name 
uci get foo.@bar[-3].name 
uci get foo.first.name 

# Mr. Second 
uci get foo.@bar[1].name 
uci get foo.@bar[-2].name 
# uci get foo.second.name 本条语句不工作,因为second没有定义 

# Mr. Third 
uci get foo.@bar[2].name 
uci get foo.@bar[-1].name 
uci get foo.third.name
复制代码

如果show,则会得到这样的值

复制代码
# uci show foo 
foo.first=bar 
foo.first.name=Mr. First 
foo.@bar[0]=bar 
foo.@bar[0].name=Mr. Second 
foo.third=bar 
foo.third.name=Mr. Third
复制代码

执行uci show foo.@bar[0]得到

# uci show foo.@bar[0] 
foo.first=bar 
foo.first.name=Mr. First

查询输出

复制代码
root@OpenWrt:~# uci -P/var/state show network.wan
uci: Entry not found
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.loopback.up=1
network.loopback.connect_time=10749
network.loopback.device=lo
network.lan=interface
network.lan.type=bridge
network.lan.proto=static
network.lan.netmask=255.255.255.0
network.lan.ipaddr=10.0.11.233
network.lan.gateway=10.0.11.254
network.lan.dns=8.8.8.8
network.lan.up=1
network.lan.connect_time=10747
network.lan.device=eth0
network.lan.ifname=br-lan
复制代码

添加防火墙规则

这个例子不仅演示了如何添加TCP SSH防火墙规则,同时也演示uci的negative (-1)语法。

复制代码
root@OpenWrt:~# uci add firewall rule 
root@OpenWrt:~# uci set firewall.@rule[-1].src=wan 
root@OpenWrt:~# uci set firewall.@rule[-1].target=ACCEPT 
root@OpenWrt:~# uci set firewall.@rule[-1].proto=tcp 
root@OpenWrt:~# uci set firewall.@rule[-1].dest_port=22 
root@OpenWrt:~# uci commit firewall 
root@OpenWrt:~# /etc/init.d/firewall restart
复制代码

获取SSID

root@OpenWrt:~# uci get wireless.@wifi-iface[-1].ssid
NGTestRouter

Get WAN IP address

  • Backfire

    uci -P/var/state get network.wan.ipaddr
  • The uci state vars are deprecated and not used anymore for network related information. In Trunk (not really uci) do

    ./lib/functions/network.sh; network_get_ipaddr ip wan; echo $ip

Get WAN interface

  • Backfire

    uci -P/var/state get network.wan.ifname
  • The uci state vars are deprecated and not used anymore for network related information. In Trunk (not really uci) do

    ./lib/functions/network.sh; network_get_device if_wan wan; echo $if_wan
  • Or

    ./lib/functions/network.sh; network_get_physdev if_wan wan; echo $if_wan

 

发表回复