OpenWrt安全模式(Failsafe)

Failsafe介绍

Failsafe可以理解为OpenWrt系统的一种安全模式,详见官网说明
OpenWrt Failsafe

OpenWrt SquashFS-Images have a built-in failsafe mode. OpenWrt failsafe mode bypasses all configuration located on the JFFS2 partition (that is the writable partition), in favor of a few hard coded defaults located on the SquashFS partition (that is the read-only partition), resulting in a device that boots up as 192.168.1.1/24 on the eth0 network interface with only essential services running.

手动进入Failsafe模式

系统在启动过程中看到有Failsafe的提示时,按f键并回车就能进入Failsafe模式,出现Failsafe提示的时候,系统状态灯会开始慢闪
Triggering via keyboard key combination in a serial console

  1. Unplug the router’s power cord.
  2. Connect the router’s WAN port directly to your PC.
  3. Configure your PC with a static IP address between 192.168.1.2 and 192.168.1.254. E. g. 192.168.1.2 (gateway and DNS is not required).
  4. Plugin the power.
  5. Connect via serial
  6. Wait until the following messages is passing: Press the [f] key and hit [enter] to enter failsafe mode
  7. Press “f” and the “enter” key
  8. You should be able to telnet (not SSH) to the router at 192.168.1.1 now (no username and password)

启动日志如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Press the [f] key and hit [enter] to enter failsafe mode
Press the [1], [2], [3] or [4] key and hit [enter] to select the debug level
f
- failsafe -


BusyBox v1.22.1 (2016-01-25 13:54:13 CST) built-in shell (ash)
Enter 'help' for a list of built-in commands.

================= FAILSAFE MODE active ================
special commands:
* firstboot reset settings to factory defaults
* mount_root mount root-partition with config files

after mount_root:
* passwd change root's password
* /etc/config directory with config files

for more help see:
http://wiki.openwrt.org/doc/howto/generic.failsafe
=======================================================

root@(none):/#

自动进入Failsafe模式

通过在启动参数中添加“failsafe=”可以让系统启动后自动进入Failsafe模式
操作步骤
进入U-Boot,修改启动参数bootargs,在最后追加“failsafe=1”(注:其实追加“failsafe=0”或者“failsafe=”也都会自动进入Failsafe模式,脚本只检查关键字“failsafe=”),如下:

1
2
ath> set bootargs "$bootargs failsafe=1"
ath> save

或者

1
2
ath> set bootargs "$bootargs failsafe=0"
ath> save

或者

1
2
ath> set bootargs "$bootargs failsafe="
ath> save

重新启动系统自动进入Failsafe模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[    7.790000] init: - preinit -
- failsafe -


BusyBox v1.22.1 (2016-01-25 13:54:13 CST) built-in shell (ash)
Enter 'help' for a list of built-in commands.

================= FAILSAFE MODE active ================
special commands:
* firstboot reset settings to factory defaults
* mount_root mount root-partition with config files

after mount_root:
* passwd change root's password
* /etc/config directory with config files

for more help see:
http://wiki.openwrt.org/doc/howto/generic.failsafe
=======================================================

root@(none):/#

Failsafe模式下的系统

在Failsafe模式下,启动脚本和配置均未加载,网络被设置为192.18.1.1,如下:

1
2
3
4
5
6
7
8
9
10
11
root@(none):/# ifconfig
eth0 Link encap:Ethernet HWaddr 00:68:BB:32:01:00
inet addr:192.168.1.1 Bcast:192.168.1.255 Mask:255.255.255.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
Interrupt:4

root@(none):/#

在Failsafe模式下,状态灯不停的闪烁,表示当前系统处于非正常状态。

Failsafe模式下升级固件

如果想在Failsafe模式下升级,可以参考
[Flash new firmware in failsafe mode]
(https://wiki.openwrt.org/doc/howto/generic.failsafe#flash_new_firmware_in_failsafe_mode)

Failsafe源码分析

Failsafe相关的源文件如下:

1
2
3
4
5
6
7
8
huzhifeng@Ubuntu1404:~/git/openwrt_trunk$ find package/base-files/ -name "*failsafe*"
package/base-files/files/etc/rc.button/failsafe
package/base-files/files/etc/banner.failsafe
package/base-files/files/lib/preinit/10_indicate_failsafe
package/base-files/files/lib/preinit/40_run_failsafe_hook
package/base-files/files/lib/preinit/99_10_failsafe_login
package/base-files/files/lib/preinit/30_failsafe_wait
huzhifeng@Ubuntu1404:~/git/openwrt_trunk$

脚本/etc/preinit

超时配置和网络配置位于文件“package/base-files/files/etc/preinit”,默认IP是192.168.1.1,等待时间是2秒钟,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
huzhifeng@Ubuntu1404:~/git/openwrt_trunk$ vim package/base-files/files/etc/preinit +14
1 #!/bin/sh
2 # Copyright (C) 2006 OpenWrt.org
3 # Copyright (C) 2010 Vertical Communications
4
5 [ -z "$PREINIT" ] && exec /sbin/init
6
7 export PATH=/bin:/sbin:/usr/bin:/usr/sbin
8
9 pi_ifname=
10 pi_ip=192.168.1.1
11 pi_broadcast=192.168.1.255
12 pi_netmask=255.255.255.0
13
14 fs_failsafe_ifname=
15 fs_failsafe_ip=192.168.1.1
16 fs_failsafe_broadcast=192.168.1.255
17 fs_failsafe_netmask=255.255.255.0
18
19 fs_failsafe_wait_timeout=2
20
21 pi_suppress_stderr="y"
22 pi_init_suppress_stderr="y"
23 pi_init_path="/bin:/sbin:/usr/bin:/usr/sbin"
24 pi_init_cmd="/sbin/init"
25
26 . /lib/functions.sh
27 . /lib/functions/preinit.sh
28 . /lib/functions/system.sh
29
30 boot_hook_init preinit_essential
31 boot_hook_init preinit_main
32 boot_hook_init failsafe
33 boot_hook_init initramfs
34 boot_hook_init preinit_mount_root
35
36 for pi_source_file in /lib/preinit/*; do
37 . $pi_source_file
38 done
39
40 boot_run_hook preinit_essential
41
42 pi_mount_skip_next=false
43 pi_jffs2_mount_success=false
44 pi_failsafe_net_message=false
45
46 boot_run_hook preinit_main

超时时间是可以通过menuconfig配置的,如下:

1
2
3
4
huzhifeng@Ubuntu1404:~/git/openwrt_trunk$ make menuconfig
[*] Image configuration --->
[*] Preinit configuration options --->
(2) Failsafe wait timeout (NEW)

其中超时时间对应的宏开关是“CONFIG_TARGET_PREINIT_TIMEOUT”,默认未配置,缺省值为2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
huzhifeng@Ubuntu1404:~/git/openwrt_trunk$ vim package/base-files/Makefile +63
59 ifneq ($(CONFIG_PREINITOPT),)
60 define ImageConfigOptions
61 mkdir -p $(1)/lib/preinit
62 echo 'pi_suppress_stderr="$(CONFIG_TARGET_PREINIT_SUPPRESS_STDERR)"' >$(1)/lib/preinit/00_preinit.conf
63 echo 'fs_failsafe_wait_timeout=$(if $(CONFIG_TARGET_PREINIT_TIMEOUT),$(CONFIG_TARGET_PREINIT_TIMEOUT),2)' >>$(1)/lib/preinit/00_preinit.conf
64 echo 'pi_init_path=$(if $(CONFIG_TARGET_INIT_PATH),$(CONFIG_TARGET_INIT_PATH),"/bin:/sbin:/usr/bin:/usr/sbin")' >>$(1)/lib/preinit/00_preinit.conf
65 echo 'pi_init_env=$(if $(CONFIG_TARGET_INIT_ENV),$(CONFIG_TARGET_INIT_ENV),"")' >>$(1)/lib/preinit/00_preinit.conf
66 echo 'pi_init_cmd=$(if $(CONFIG_TARGET_INIT_CMD),$(CONFIG_TARGET_INIT_CMD),"/sbin/init")' >>$(1)/lib/preinit/00_preinit.conf
67 echo 'pi_init_suppress_stderr="$(CONFIG_TARGET_INIT_SUPPRESS_STDERR)"' >>$(1)/lib/preinit/00_preinit.conf
68 echo 'pi_ifname=$(if $(CONFIG_TARGET_PREINIT_IFNAME),$(CONFIG_TARGET_PREINIT_IFNAME),"")' >>$(1)/lib/preinit/00_preinit.conf
69 echo 'pi_ip=$(if $(CONFIG_TARGET_PREINIT_IP),$(CONFIG_TARGET_PREINIT_IP),"192.168.1.1")' >>$(1)/lib/preinit/00_preinit.conf
70 echo 'pi_netmask=$(if $(CONFIG_TARGET_PREINIT_NETMASK),$(CONFIG_TARGET_PREINIT_NETMASK),"255.255.255.0")' >>$(1)/lib/preinit/00_preinit.conf
71 echo 'pi_broadcast=$(if $(CONFIG_TARGET_PREINIT_BROADCAST),$(CONFIG_TARGET_PREINIT_BROADCAST),"192.168.1.255")' >>$(1)/lib/preinit/00_preinit.conf
72 echo 'pi_preinit_net_messages="$(CONFIG_TARGET_PREINIT_SHOW_NETMSG)"' >>$(1)/lib/preinit/00_preinit.conf
73 echo 'pi_preinit_no_failsafe_netmsg="$(CONFIG_TARGET_PREINIT_SUPPRESS_FAILSAFE_NETMSG)"' >>$(1)/lib/preinit/00_preinit.conf
74 endef
75 endif

脚本30_failsafe_wait

Failsafe对应的脚本文件是“package/base-files/files/lib/preinit/30_failsafe_wait”,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
huzhifeng@Ubuntu1404:~/git/openwrt_trunk$ vim package/base-files/files/lib/preinit/30_failsafe_wait
1 #!/bin/sh
2 # Copyright (C) 2006-2010 OpenWrt.org
3 # Copyright (C) 2010 Vertical Communications
4
5 fs_wait_for_key () {
6 local timeout=$3
7 local timer
8 local do_keypress
9 local keypress_true="$(mktemp)"
10 local keypress_wait="$(mktemp)"
11 local keypress_sec="$(mktemp)"
12 if [ -z "$keypress_wait" ]; then
13 keypress_wait=/tmp/.keypress_wait
14 touch $keypress_wait
15 fi
16 if [ -z "$keypress_true" ]; then
17 keypress_true=/tmp/.keypress_true
18 touch $keypress_true
19 fi
20 if [ -z "$keypress_sec" ]; then
21 keypress_sec=/tmp/.keypress_sec
22 touch $keypress_sec
23 fi
24
25 trap "echo 'true' >$keypress_true; lock -u $keypress_wait ; rm -f $keypress_wait" INT
26 trap "echo 'true' >$keypress_true; lock -u $keypress_wait ; rm -f $keypress_wait" USR1
27
28 [ -n "$timeout" ] || timeout=1
29 [ $timeout -ge 1 ] || timeout=1
30 timer=$timeout
31 lock $keypress_wait
32 {
33 while [ $timer -gt 0 ]; do
34 echo "$timer" >$keypress_sec
35 timer=$(($timer - 1))
36 sleep 1
37 done
38 lock -u $keypress_wait
39 rm -f $keypress_wait
40 } &
41
42 echo "Press the [$1] key and hit [enter] $2"
43 echo "Press the [1], [2], [3] or [4] key and hit [enter] to select the debug level"
44 # if we're on the console we wait for input
45 {
46 while [ -r $keypress_wait ]; do
47 timer="$(cat $keypress_sec)"
48
49 [ -n "$timer" ] || timer=1
50 timer="${timer%%\ *}"
51 [ $timer -ge 1 ] || timer=1
52 do_keypress=""
53 {
54 read -t "$timer" do_keypress
55 case "$do_keypress" in
56 $1)
57 echo "true" >$keypress_true
58 ;;
59 1 | 2 | 3 | 4)
60 echo "$do_keypress" >/tmp/debug_level
61 ;;
62 *)
63 continue;
64 ;;
65 esac
66 lock -u $keypress_wait
67 rm -f $keypress_wait
68 }
69 done
70 }
71 lock -w $keypress_wait
72
73 keypressed=1
74 [ "$(cat $keypress_true)" = "true" ] && keypressed=0
75
76 rm -f $keypress_true
77 rm -f $keypress_wait
78 rm -f $keypress_sec
79
80 return $keypressed
81 }
82
83 failsafe_wait() {
84 FAILSAFE=
85 grep -q 'failsafe=' /proc/cmdline && FAILSAFE=true && export FAILSAFE
86 if [ "$FAILSAFE" != "true" ]; then
87 pi_failsafe_net_message=true
88 preinit_net_echo "Please press button now to enter failsafe"
89 pi_failsafe_net_message=false
90 fs_wait_for_key f 'to enter failsafe mode' $fs_failsafe_wait_timeout && FAILSAFE=true
91 [ -f "/tmp/failsafe_button" ] && FAILSAFE=true && echo "- failsafe button "`cat /tmp/failsafe_button`" was pressed -"
92 [ "$FAILSAFE" = "true" ] && export FAILSAFE && touch /tmp/failsafe
93 fi
94 }
95
96 boot_hook_add preinit_main failsafe_wait

脚本10_indicate_failsafe

进入Failsafe模式时执行的脚本是“package/base-files/files/lib/preinit/10_indicate_failsafe”,进入后设置状态灯为快闪模式,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
huzhifeng@Ubuntu1404:~/git/openwrt_trunk$ vim package/base-files/files/lib/preinit/10_indicate_failsafe
1 #!/bin/sh
2 # Copyright (C) 2006 OpenWrt.org
3 # Copyright (C) 2010 Vertical Communications
4
5 # commands for emitting messages to network in failsafe mode
6
7 indicate_failsafe_led () {
8 set_state failsafe
9 }
10
11 indicate_failsafe() {
12 echo "- failsafe -"
13 preinit_net_echo "Entering Failsafe!\n"
14 indicate_failsafe_led
15 }
16
17 boot_hook_add failsafe indicate_failsafe

脚本40_run_failsafe_hook

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
代码如下:
huzhifeng@Ubuntu1404:~/git/openwrt_trunk$ cat -n package/base-files/files/lib/preinit/40_run_failsafe_hook
1 #!/bin/sh
2 # Copyright (C) 2006-2010 OpenWrt.org
3 # Copyright (C) 2010 Vertical Communications
4
5 run_failsafe_hook() {
6 if [ "$FAILSAFE" = "true" ]; then
7 boot_run_hook failsafe
8 lock -w /tmp/.failsafe
9 fi
10 }
11
12 boot_hook_add preinit_main run_failsafe_hook
huzhifeng@Ubuntu1404:~/git/openwrt_trunk$

脚本99_10_failsafe_login

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
huzhifeng@Ubuntu1404:~/git/openwrt_trunk$ cat -n package/base-files/files/lib/preinit/99_10_failsafe_login
1 #!/bin/sh
2 # Copyright (C) 2006 OpenWrt.org
3 # Copyright (C) 2010 Vertical Communications
4
5 failsafe_netlogin () {
6 telnetd -l /bin/login.sh <> /dev/null 2>&1
7 }
8
9 failsafe_shell() {
10 lock /tmp/.failsafe
11 ash --login
12 echo "Please reboot system when done with failsafe network logins"
13 }
14
15 boot_hook_add failsafe failsafe_netlogin
16 boot_hook_add failsafe failsafe_shell
huzhifeng@Ubuntu1404:~/git/openwrt_trunk$

脚本/etc/rc.button/failsafe

1
2
3
4
5
6
代码如下:
huzhifeng@Ubuntu1404:~/git/openwrt_trunk$ cat -n package/base-files/files/etc/rc.button/failsafe
1 #!/bin/sh
2
3 [ "${TYPE}" = "switch" ] || echo ${BUTTON} > /tmp/failsafe_button
huzhifeng@Ubuntu1404:~/git/openwrt_trunk$

脚本/etc/banner.failsafe

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
huzhifeng@Ubuntu1404:~/git/openwrt_trunk$ cat -n package/base-files/files/etc/banner.failsafe
1 ================= FAILSAFE MODE active ================
2 special commands:
3 * firstboot reset settings to factory defaults
4 * mount_root mount root-partition with config files
5
6 after mount_root:
7 * passwd change root's password
8 * /etc/config directory with config files
9
10 for more help see:
11 http://wiki.openwrt.org/doc/howto/generic.failsafe
12 =======================================================
13
huzhifeng@Ubuntu1404:~/git/openwrt_trunk$

禁用Failsafe模式

以下代码片段是我做的一个patch
huzhifeng/openwrt-disable-failsafe-mode.patch

参考

How to disable FailSafe Mode
Allow to disable failsafe
base-files: Allow to disable failsafe mode

EOF