简述 #
Netifrc 是 Gentoo 在运行 OpenRC 的系统上,用于配置和管理网络接口的默认框架。
源码 #
由 kimi-k3 注释,仅供参考。
1#!/sbin/openrc-run
2# 声明这是 OpenRC 服务脚本,由 openrc-run 解释执行(而不是普通的 sh/bash)
3# shellcheck shell=sh disable=SC1008
4# Copyright (c) 2007-2009 Roy Marples <roy@marples.name>
5# Copyright (c) 2010-2016 Gentoo Foundation
6# Released under the 2-clause BSD license.
7
8# ---- 全局常量定义 ----
9SHDIR="/lib/netifrc/sh" # 共享 shell 函数库目录(functions.sh 等)
10MODULESDIR="/lib/netifrc/net" # 网络模块目录(dhcpcd.sh、iproute2.sh、wireless.sh 等)
11_config_vars="config metric routes" # 允许按"接口类型/接口名"覆盖的配置变量列表
12
13# IN_BACKGROUND 表示本服务是否由后台事件(如 devd/udev 热插拔)触发启动。
14# 后台模式下 stop 时不会真正关闭接口(例如 PPP 按需拨号场景)
15# -z : 判断字符串长度为 0
16[ -z "${IN_BACKGROUND}" ] && IN_BACKGROUND="NO"
17
18# shellcheck disable=SC2034
19# OpenRC 服务描述(rc-service ... describe 显示)
20description="Configures network interfaces."
21
22# 定义一个包含"换行符"的变量,用于之后把 IFS 设为换行符,
23# 从而实现"按行分割配置数组"(netifrc 用换行分隔多值配置项)
24__IFS="
25"
26
27# 如果此文件被直接调用(而非经由 OpenRC),默认 INIT 系统为 openrc,
28# 这个变量会被 functions.sh 中的函数引用
29# 当 INIT 未设置或为空,返回 openrc 并设置 INIT=openrc
30: "${INIT:=openrc}"
31
32# ---- 加载共享函数库 ----
33# functions.sh 提供 _exists、_up、_down、_add_address、_add_route、
34# get_interface、shell_var、yesno、einfo/ewarn/eerror 包装等核心函数
35if [ -f "$SHDIR/functions.sh" ]; then
36 # shellcheck disable=SC1090
37 . "$SHDIR/functions.sh"
38else
39 echo "$SHDIR/functions.sh missing. Exiting"
40 exit 1
41fi
42
43# 为每个接口生成独立的"模块排序缓存文件"路径。
44# get_interface 从 RC_SVCNAME(如 net.eth0)中提取接口名(eth0)。
45# 按接口分开缓存可避免多接口并发启动时的竞态条件,
46# 也允许每个接口使用不同的自定义模块集合。
47# RC_* 的变量由 OpenRC 设置,net.lo 只是直接继承使用
48MODULESLIST="${RC_SVCDIR}/nettree$(get_interface)"
49
50# ====================================================================
51# depend() —— OpenRC 依赖声明函数
52# 由 rc 依赖解析器在生成依赖树时调用(不是运行时调用)
53# ====================================================================
54depend()
55{
56 local IFACE IFVAR
57 IFACE=$(get_interface) # 接口名,如 eth0
58 IFVAR=$(shell_var "${IFACE}") # 转成合法 shell 变量名,如 eth0( wlan0-1 -> wlan0_1 )
59
60 # Linux 下非 lo 接口:必须先挂载 sysfs(读取 /sys/class/net),
61 # 且在内核模块加载(modules 服务)之后启动
62 if [ "$RC_UNAME" = Linux ] && [ "$IFACE" != lo ]; then
63 need sysfs
64 after modules
65 fi
66 after bootmisc # 在 bootmisc 之后(保证 /run 等就绪)
67 keyword -jail -prefix -vserver # 声明在 jail/prefix/vserver 环境中不自动加入运行级别
68
69 case "${IFACE}" in
70 lo|lo0) ;; # 回环接口:无额外依赖(最底层)
71 *) # net.lo0 是 BSD 系统上的回环(loopback)接口服务,兼容 BSD 系统
72 after net.lo net.lo0 dbus # 其他接口在 lo 和 dbus 之后
73 need localmount # 需要本地文件系统已挂载(模块文件在磁盘上)
74 provide net # 提供虚拟服务 "net",让依赖 net 的服务(如 sshd)可以启动
75 ;;
76 esac
77
78 # 允许用户为特定接口定义 depend_<接口> 函数来追加依赖
79 if [ "$(command -v "depend_${IFVAR}")" = "depend_${IFVAR}" ]; then
80 "depend_${IFVAR}"
81 fi
82
83 # 兼容旧式变量 rc_need_eth0 / rc_use_eth0 等,转换为新式 rc_net_eth0_need 形式
84 local dep prov
85 for dep in need use before after provide keyword; do
86 eval prov=\$rc_${dep}_${IFVAR}
87 if [ -n "${prov}" ]; then
88 "${dep}" "${prov}"
89 ewarn "rc_${dep}_${IFVAR} is deprecated."
90 ewarn "Please use rc_net_${IFVAR}_${dep} instead."
91 fi
92 done
93}
94
95# ====================================================================
96# 数组处理辅助函数
97# netifrc 新式配置用"换行分隔的字符串"模拟数组;
98# 老配置可能是 bash 数组或空格分隔字符串,这里做兼容。
99# ====================================================================
100
101# _array_helper <变量名>
102# 读取变量值,去掉每行首尾空白、删除空行、把行内连续空白压缩成单个空格,
103# 然后按行输出(供调用方以 IFS=换行 方式读回)
104_array_helper()
105{
106 local _a=
107
108 eval _a=\$$1
109 _a=$(echo "${_a}" | sed -e 's:^[[:space:]]*::' -e 's:[[:space:]]*$::' -e '/^$/d' -e 's:[[:space:]]\{1,\}: :g')
110
111 [ -n "${_a}" ] && printf "%s\n" "${_a}"
112}
113
114# _get_array <变量名>
115# 以"每行一个元素"的形式输出配置变量的内容。
116# 若运行在 bash 且该变量是 bash 数组(旧式写法),则给出弃用警告并逐元素输出。
117_get_array()
118{
119 local _a=''
120 if [ -n "${BASH}" ]; then
121 # shellcheck disable=SC2039
122 case "$(declare -p "$1" 2>/dev/null)" in
123 "declare -a "*)
124 ewarn "You are using a bash array for $1."
125 ewarn "This feature will be removed in the future."
126 ewarn "Please see net.example for the correct format for $1."
127 eval "set -- \"\${$1[@]}\"" # 把数组元素展开为位置参数
128 for _a; do
129 printf "%s\n" "${_a}" # 每元素一行输出
130 done
131 return 0
132 ;;
133 esac
134 fi
135
136 _array_helper "$1" # 非 bash 数组:走普通字符串路径
137}
138
139# _flatten_array <变量名>
140# 把(可能是 bash 数组的)变量压平为单行 shell 引号包裹的字符串,
141# 用于把配置原样传递/嵌入到 eval 语句中(注意对单引号的转义处理)
142_flatten_array()
143{
144 if [ -n "${BASH}" ]; then
145 # shellcheck disable=SC2039
146 case "$(declare -p "$1" 2>/dev/null)" in
147 "declare -a "*)
148 ewarn "You are using a bash array for $1."
149 ewarn "This feature will be removed in the future."
150 ewarn "Please see net.example for the correct format for $1."
151 eval "set -- \"\${$1[@]}\""
152 for x; do
153 # shellcheck disable=SC2059
154 # 先 printf 解释转义,再把 ' 替换为 '\'' 以安全嵌入单引号串
155 printf "'%s' " "$(printf "$x" | sed "s:':'\\\'':g")"
156 done
157 return 0
158 ;;
159 esac
160 fi
161
162 _array_helper "$1"
163}
164
165# ====================================================================
166# _wait_for_presence —— 等待接口出现在系统中
167# 用于 USB 网卡等可能需要时间才被内核创建的设备
168# 超时由 presence_timeout_<IFVAR> 或全局 presence_timeout 控制(秒)
169# ====================================================================
170_wait_for_presence()
171{
172 local timeout=
173 local rc=
174
175 _exists && return 0 # 已存在则直接返回
176
177 eval timeout=\$presence_timeout_${IFVAR}
178 timeout=${timeout:-${presence_timeout:-0}}
179
180 [ ${timeout} -le 0 ] && return 1 # 未配置等待 -> 直接失败
181
182 einfon "Waiting for ${IFACE} to show up (${timeout} seconds)"
183 while [ ${timeout} -gt 0 ]; do
184 _exists
185 rc=$?
186 [ $rc -eq 0 ] && break
187
188 sleep 1
189 printf "."
190 : $(( timeout -= 1 ))
191 done
192
193 if [ ${timeout} -le 0 ]; then
194 _exists # 最后再检查一次
195 rc=$?
196 fi
197 echo
198 eend $rc # 输出 [ ok ] / [ !! ] 并返回状态码
199}
200
201# ====================================================================
202# _wait_for_carrier —— 等待链路载波(网线插入/无线关联成功)
203# 超时由 carrier_timeout_<IFVAR> 或全局 carrier_timeout 控制
204# 注意:超时<=0 时返回 0(成功),即"用户不想等就不等",
205# 与 _wait_for_presence 的语义相反
206# ====================================================================
207_wait_for_carrier()
208{
209 local timeout=
210
211 _has_carrier && return 0
212
213 eval timeout=\$carrier_timeout_${IFVAR}
214 timeout=${timeout:-${carrier_timeout:-0}}
215
216 # 如果用户不需要这个特性
217 [ ${timeout} -le 0 ] && return 0
218
219 einfon "Waiting for carrier (${timeout} seconds) "
220 while [ ${timeout} -gt 0 ]; do
221 if _has_carrier; then
222 echo
223 eend 0
224 return 0
225 fi
226 sleep 1
227 : $(( timeout -= 1 ))
228 printf "."
229 done
230
231 echo
232 eend 1
233 return 1
234}
235
236# ====================================================================
237# _netmask2cidr —— 把子网掩码转换为 CIDR 前缀长度
238# 支持点分十进制(255.255.255.0)和十六进制(0xffffff00)两种形式
239# 算法:对掩码的每个字节统计二进制中 1 的个数并累加
240# ====================================================================
241_netmask2cidr()
242{
243 # 某些 shell(FreeBSD sh、dash、busybox)不能正确处理十六进制算术,
244 # 这里把 0xffffff00 拆成 0xff.0xff.0xff.0x00 的形式绕过该 bug。
245 # bash 和 NetBSD sh 不需要此处理。
246 case $1 in
247 0x*)
248 local hex=${1#0x*} quad=
249 while [ -n "${hex}" ]; do
250 local lastbut2=${hex#??*} # 去掉前两个十六进制字符
251 quad=${quad}${quad:+.}0x${hex%${lastbut2}*} # 取出前两位拼成 0xNN 段
252 hex=${lastbut2}
253 done
254 # shellcheck disable=SC2086
255 set -- ${quad} # 重新设置为点分形式继续处理
256 ;;
257 esac
258
259 local i='' len=''
260 local IFS=. # 按点分段
261 for i in $1; do
262 case $i in
263 0x*) i=$((i)) ;; # 十六进制段先转十进制
264 esac
265 while [ ${i} -ne 0 ]; do # 统计该字节中 1 的位数
266 : $(( len += i % 2 ))
267 : $(( i >>= 1 ))
268 done
269 done
270
271 echo "${len}" # 最后 len 的值是掩码二进制中 1 的个数
272}
273
274# ====================================================================
275# _configure_variables —— 实现配置变量的"回退查找"
276# 对 _config_vars 里的每个变量(config/metric/routes),
277# 依次按位置参数 $1 $2 ...(通常是 接口类型、接口名 之类的层级)
278# 查找 config_$1、config_$2 ...,找到第一个非空的就赋给 config_<IFVAR>
279# 用法示例:_configure_variables $iface_type → 允许按接口类型统一配置
280# ====================================================================
281_configure_variables()
282{
283 local var='' v='' t=''
284
285 for var in ${_config_vars}; do
286 local v=
287 for t; do
288 eval v="\"\$${var}_${t}\""
289 if [ -n "${v}" ]; then
290 eval "${var}_${IFVAR}=\"\$${var}_${t}\""
291 continue 2 # 跳到下一个 var
292 fi
293 done
294 done
295}
296
297# ====================================================================
298# _which —— 在 PATH 中查找可执行文件,输出完整路径
299# (不依赖外部 which 命令,纯 shell 实现)
300# ====================================================================
301_which()
302{
303 local i OIFS
304 # Empty
305 [ -z "$1" ] && return
306 # check paths
307 OIFS="$IFS"
308 IFS=:
309 for i in $PATH ; do
310 [ -x "$i/$1" ] && echo "$i/$1" && break
311 done
312 IFS=$OIFS
313}
314
315# ====================================================================
316# _program_available —— 检查程序是否可用(支持多个候选、shell 内建命令)
317# 参数为空返回 0;找到第一个可用的候选就输出它并返回 0
318# 绝对路径直接测 -x;裸命令名用 type(能识别 builtin/alias/函数)
319# ====================================================================
320_program_available()
321{
322 [ -z "$1" ] && return 0
323 local x=
324 # for x; do 是 for x in "$@"; do 的省略写法
325 for x; do
326 case "${x}" in
327 # 以 / 开头 → 绝对路径
328 # 直接测"文件存在且可执行",成功就 break 跳出循环
329 /*) [ -x "${x}" ] && break;;
330 # 用 shell 内建的 type 探测(屏蔽它的输出和报错),成功就 break
331 *) type "${x}" >/dev/null 2>&1 && break;;
332 esac
333 x=
334 done
335 [ -n "${x}" ] && echo $x && return 0
336 return 1
337}
338
339# 显示接口当前获取到的 IPv4 / IPv6 地址(供 DHCP 等模块回调打印日志)
340_show_address()
341{
342 einfo "received address $(_get_inet_address "${IFACE}")"
343}
344
345# ====================================================================
346# _get_errorhandler_behavior —— 查询"错误处理策略"配置
347# 当添加地址/路由遇到 EEXIST(已存在)等错误时,用户可配置忽略/告警/失败。
348# 按从具体到一般的顺序查找变量,找到第一个非空值即返回:
349# errh_<IFVAR>_<对象>_<错误> → errh_<IFVAR>_<对象>_DEFAULT
350# → errh_<IFVAR>_DEFAULT_<错误> → errh_<IFVAR>_DEFAULT_DEFAULT
351# → errh_DEFAULT_... → 最后回退到传入的 fallback
352# 典型配置:
353# errh_eth0_address_EEXIST=warn # 地址已存在只警告
354# errh_eth0_route_EEXIST=warn
355# ====================================================================
356_get_errorhandler_behavior() {
357 IFVAR="$1"
358 object="$2"
359 error="$3"
360 fallback="$4"
361 value=
362 for key in \
363 "errh_${IFVAR}_${object}_${error}" \
364 "errh_${IFVAR}_${object}_DEFAULT" \
365 "errh_${IFVAR}_DEFAULT_${error}" \
366 "errh_${IFVAR}_DEFAULT_DEFAULT" \
367 "errh_DEFAULT_${object}_${error}" \
368 "errh_DEFAULT_${object}_DEFAULT" \
369 "errh_DEFAULT_DEFAULT_${error}" \
370 "errh_DEFAULT_DEFAULT_DEFAULT" \
371 "errh" \
372 "fallback" ; do
373 eval value="\${${key}}"
374 if [ -n "$value" ]; then
375 echo "$value" && break
376 fi
377 done
378}
379
380_show_address6()
381{
382 einfo "received address $(_get_inet6_address "${IFACE}")"
383}
384
385# ====================================================================
386# _gen_module_list —— 扫描所有模块并做拓扑排序,缓存结果
387#
388# 每个模块文件(/lib/netifrc/net/xxx.sh)里定义了 <mod>_depend() 函数,
389# 其中调用 after/before/program/provide 声明自己的依赖关系:
390# after dhcp —— 本模块要在提供 "dhcp" 的模块之后
391# before net.lo —— (before 是反向的 after)
392# program dhcpcd —— 本模块需要系统里存在 dhcpcd 程序
393# provide dhcp —— 本模块提供 "dhcp" 这个虚拟能力
394#
395# 本函数:
396# 1) 检查缓存(nettree 文件)是否比模块目录新,新则直接复用
397# 2) 否则在子 shell 中加载所有模块、收集依赖、DFS 拓扑排序
398# 3) 把排序结果以 shell 变量赋值形式原子地写入缓存文件
399# ====================================================================
400_gen_module_list()
401{
402 local x='' f='' force="$1"
403 if ! ${force} ; then
404 # 缓存文件比本进程还新?说明时间戳异常(如系统时钟回拨),弃用
405 if [ -s "${MODULESLIST}" ] && [ "${MODULESLIST}" -nt /proc/$$/status ]; then
406 ewarn "Discarding cached module list ($MODULESLIST) as it's newer current time!"
407 # 缓存比模块目录新时,再逐个检查有没有比缓存更新的模块文件
408 elif [ -s "${MODULESLIST}" ] && [ "${MODULESLIST}" -nt "${MODULESDIR}" ]; then
409 local update=false
410 for x in "${MODULESDIR}"/*.sh; do
411 [ -e "${x}" ] || continue
412 if [ "${x}" -nt "${MODULESLIST}" ]; then
413 update=true
414 break
415 fi
416 done
417 ${update} || return 0 # 缓存有效,直接返回
418 fi
419 fi
420
421 einfo "Caching network module dependencies"
422 # 在子 shell 中执行,保护主脚本的变量命名空间不被模块污染
423 (
424 # ---- 供模块 <mod>_depend() 调用的依赖声明原语 ----
425
426 # after X:把 X 追加到当前模块的 after 列表
427 after() {
428 eval ${MODULE}_after="\"\${${MODULE}_after}\${${MODULE}_after:+ }$*\""
429 }
430
431 # before Y:等价于让 Y after 当前模块
432 before() {
433 local mod=${MODULE}
434 local MODULE=
435 for MODULE; do
436 after "${mod}"
437 done
438 }
439
440 # program [start|stop] PROG...:声明模块依赖的外部程序
441 # 可以分别声明启动/停止阶段需要的程序
442 program() {
443 if [ "$1" = "start" ] || [ "$1" = "stop" ]; then
444 local s="$1"
445 shift
446 eval ${MODULE}_program_${s}="\"\${${MODULE}_program_${s}}\${${MODULE}_program_${s}:+ }$*\""
447 else
448 eval ${MODULE}_program="\"\${${MODULE}_program}\${${MODULE}_program:+ }$*\""
449 fi
450 }
451
452 # provide V:声明本模块提供虚拟能力 V;
453 # 同时记录反向映射 V_providedby(谁提供了 V)
454 provide() {
455 eval ${MODULE}_provide="\"\${${MODULE}_provide}\${${MODULE}_provide:+ }$*\""
456 local x
457 for x in "$@"; do
458 eval ${x}_providedby="\"\${${MODULE}_providedby}\${${MODULE}_providedby:+ }${MODULE}\""
459 done
460 }
461
462 # ---- 第一阶段:加载所有模块并执行其 <mod>_depend ----
463 for MODULE in "${MODULESDIR}"/*.sh; do
464 sh -n "${MODULE}" || continue # 先做语法检查,失败跳过
465 # shellcheck disable=SC1090
466 . "${MODULE}" || continue # source 模块以定义其 _depend 函数
467 MODULE=${MODULE#${MODULESDIR}/} # 去掉目录前缀
468 MODULE=${MODULE%.sh} # 去掉 .sh 后缀 → 模块名
469 eval "${MODULE}_depend" # 执行依赖声明
470 MODULES="${MODULES} ${MODULE}"
471 done
472
473 # ---- 第二阶段:DFS 拓扑排序 ----
474 VISITED=
475 SORTED=
476 visit() {
477 case " ${VISITED} " in
478 *" $1 "*) return;; # 已访问过,防环
479 esac
480 VISITED="${VISITED} $1"
481
482 # 先递归访问本模块 after 的所有目标
483 # (如果 after 的是虚拟能力名,则展开为提供它的所有模块)
484 eval AFTER=\$${1}_after
485 for MODULE1 in ${AFTER}; do
486 eval PROVIDEDBY=\$${MODULE1}_providedby
487 if [ -n "${PROVIDEDBY}" ]; then
488 for MODULE2 in ${PROVIDEDBY}; do
489 visit "${MODULE2}"
490 done
491 else
492 visit "${MODULE1}"
493 fi
494 done
495
496 # 再递归访问本模块 provide 的虚拟能力(保证提供者先于使用"能力名"者被处理?——
497 # 实际效果是建立 provide 关系的遍历完整性)
498 eval PROVIDE=\$${1}_provide
499 for MODULE in ${PROVIDE}; do
500 visit "${MODULE}"
501 done
502
503 # 只有"真实模块"(不提供任何虚拟能力的叶子,或未被登记为提供者)
504 # 才被加入排序结果;提供虚拟能力的模块会通过 providedby 间接进入
505 eval PROVIDEDBY=\$${1}_providedby
506 [ -z "${PROVIDEDBY}" ] && SORTED="${SORTED} $1"
507 }
508
509 for MODULE in ${MODULES}; do
510 visit "${MODULE}"
511 done
512
513 # ---- 第三阶段:原子写入缓存文件 ----
514 # 缓存格式为 shell 变量赋值,之后直接 source 即可
515 TMPMODULESLIST=${MODULESLIST}.$$
516 printf "" > "${TMPMODULESLIST}"
517 i=0
518 for MODULE in ${SORTED}; do
519 eval PROGRAM=\$${MODULE}_program
520 eval PROGRAM_START=\$${MODULE}_program_start
521 eval PROGRAM_STOP=\$${MODULE}_program_stop
522 eval PROVIDE=\$${MODULE}_provide
523 echo "module_${i}='${MODULE}'"
524 echo "module_${i}_program='${PROGRAM}'"
525 echo "module_${i}_program_start='${PROGRAM_START}'"
526 echo "module_${i}_program_stop='${PROGRAM_STOP}'"
527 echo "module_${i}_provide='${PROVIDE}'"
528 : $(( i += 1 ))
529 done >> "${TMPMODULESLIST}"
530 echo "module_${i}=" >> "${TMPMODULESLIST}" # 空值作为列表终止标记
531 mv -f "${TMPMODULESLIST}" "${MODULESLIST}" # mv 保证原子替换
532 )
533
534 return 0
535}
536
537# ====================================================================
538# _load_modules —— 按排序结果实际加载模块,处理虚拟能力包装
539# 参数 $1:true=启动阶段加载,false=停止阶段加载
540# ====================================================================
541_load_modules()
542{
543 local starting=$1 mymods=
544
545 # Ensure our list is up to date
546 _gen_module_list false # 生成/复用模块排序缓存
547 # shellcheck disable=SC1090
548 if ! . "${MODULESLIST}"; then # source 缓存失败则强制重建
549 _gen_module_list true
550 # shellcheck disable=SC1090
551 . "${MODULESLIST}"
552 fi
553
554 MODULES=
555 # 非回环接口:读取用户配置 modules_<IFVAR>(如 modules_eth0="dhcpcd")
556 # 未配置则用全局 modules 变量,作为"用户偏好模块"列表
557 if [ "${IFACE}" != "lo" ] && [ "${IFACE}" != "lo0" ]; then
558 eval mymods=\$modules_${IFVAR}
559 # shellcheck disable=SC2154
560 [ -z "${mymods}" ] && mymods=${modules}
561 fi
562
563 local i=-1 x='' mod='' f='' provides=''
564 while true; do
565 : $(( i += 1 ))
566 eval mod=\$module_${i}
567 [ -z "${mod}" ] && break # 空值 = 列表结束
568 [ -e "${MODULESDIR}/${mod}.sh" ] || continue
569
570 # 检查模块声明的通用程序依赖是否满足,不满足则跳过该模块
571 eval set -- \$module_${i}_program
572 if [ -n "$1" ]; then
573 if ! _program_available "$@" >/dev/null; then
574 vewarn "Skipping module $mod due to missing program: $*"
575 continue
576 fi
577 fi
578 # 再检查阶段特定(start/stop)的程序依赖
579 if ${starting}; then
580 eval set -- \$module_${i}_program_start
581 else
582 eval set -- \$module_${i}_program_stop
583 fi
584 if [ -n "$1" ]; then
585 if ! _program_available "$@" >/dev/null; then
586 vewarn "Skipping module $mod due to missing program: $*"
587 continue
588 fi
589 fi
590
591 eval provides=\$module_${i}_provide
592 if ${starting}; then
593 # 用户可用 "!模块名" 或 "!虚拟能力" 显式禁用某模块
594 # 例如 modules_eth0="!wpa_supplicant"
595 case " ${mymods} " in
596 *" !${mod} "*) continue;;
597 *" !${provides} "*) [ -n "${provides}" ] && continue;;
598 esac
599 fi
600 MODULES="${MODULES}${MODULES:+ }${mod}"
601
602 # 真正 source 模块文件,把 <mod>_pre_start/_start/_stop 等函数载入
603 # shellcheck disable=SC1090
604 if ! . "${MODULESDIR}/${mod}.sh"; then
605 eend 1 "${RC_SVCNAME}: error loading module \`${mod}'"
606 exit 1
607 fi
608
609 [ -z "${provides}" ] && continue
610
611 # ---- 虚拟能力包装 ----
612 # 如果模块提供虚拟能力(如 dhcp),则定义 dhcp_pre_start/dhcp_start/
613 # dhcp_post_start 包装函数,转发到真实模块的实现。
614 # 这样 config_eth0="dhcp" 时,主流程调用 dhcp_start 即可,
615 # 无需关心底层是 dhcpcd 还是 dhclient。
616 local f=
617 for f in pre_start start post_start; do
618 inner=$(command -v "${mod}_${f}")
619 eval "${provides}_${f}() { [ '${inner}' = '${mod}_${f}' ] || return 0; ${mod}_${f} \"\$@\"; }"
620 done
621
622 eval module_${mod}_provides="${provides}" # 记录 mod -> 能力
623 eval module_${provides}_providedby="${mod}" # 记录 能力 -> mod(当前提供者)
624 done
625
626 # ---- 用户偏好优先 ----
627 # 如果 mymods 里显式列出了某模块(如 modules_eth0="dhcpcd"),
628 # 则把该模块提供的虚拟能力包装改指向它(覆盖默认提供者),
629 # 实现"多种 dhcp 客户端中用户指定用哪个"
630 for mod in ${mymods}; do
631 case " ${MODULES} " in
632 *" ${mod} "*)
633 eval x=\$module_${mod}_provides
634 [ -z "${x}" ] && continue
635 for f in pre_start start post_start; do
636 inner=$(command -v "${mod}_${f}")
637 eval "${x}_${f}() { [ '${inner}' = '${mod}_${f}' ] || return 0; ${mod}_${f} \"\$@\"; }"
638 done
639 eval module_${x}_providedby="${mod}" # 修改能力提供者
640 ;;
641 esac
642 done
643
644 # ---- 收尾 ----
645 # 启动时:去掉"被别的模块替代提供虚拟能力"的重复模块
646 # (例如用户指定 dhcpcd,则 dhclient 被从列表剔除)
647 # 停止时:反转列表,让模块以相反顺序停止(类似栈的 LIFO)
648 local LIST="${MODULES}" p=
649 MODULES=
650 if ${starting}; then
651 for mod in ${LIST}; do
652 eval x=\$module_${mod}_provides
653 if [ -n "${x}" ]; then
654 eval p=\$module_${x}_providedby
655 [ "${mod}" != "${p}" ] && continue # 该能力已由别的模块提供 → 丢弃
656 fi
657 MODULES="${MODULES}${MODULES:+ }${mod}"
658 done
659 else
660 for mod in ${LIST}; do
661 MODULES="${mod}${MODULES:+ }${MODULES}" # 头插法反转
662 done
663 fi
664
665 veinfo "Loaded modules: ${MODULES}"
666}
667
668# ====================================================================
669# _load_config —— 解析接口配置到 config_0, config_1, ... 伪数组
670# 配置来源(/etc/conf.d/net):
671# config_eth0="192.168.1.10/24
672# 192.168.2.10/24"
673# fallback_eth0="dhcp" ← 主配置失败时的后备
674# 结果存为 config_0='...' config_1='...' config_2= (空为终止)
675# ====================================================================
676_load_config()
677{
678 local config='' fallback=''
679 config="$(_get_array "config_${IFVAR}")"
680 fallback="$(_get_array fallback_${IFVAR})"
681
682 config_index=0
683 local IFS="$__IFS" # IFS=换行:按行拆分
684 set -- ${config}
685
686 # 兼容"空格分隔的多个 CIDR 写在同一行"的旧式写法:
687 # config_eth0="10.0.0.1/24 10.0.0.2/24"
688 # 但如果空格后面的内容不是 IP 地址(而是参数,如 "dhcp" 的选项),
689 # 则不按空格拆分,仍按整行处理
690 if [ $# = 1 ]; then
691 unset IFS
692 set -- ${config}
693 # 如果第 2 个字段既不含 . 也不含 :,说明它不是 IP 地址,
694 # 而是第一个配置的参数 → 恢复按换行拆分
695 if [ "${2#*.*}" = "${2}" ] && [ "${2#*:*}" = "${2}" ]; then
696 # Not an IPv4/IPv6
697 local IFS="$__IFS"
698 set -- ${config}
699 fi
700 fi
701
702 # 回环接口强制配置 127.0.0.1/8(除非用户显式写了 "null")
703 if [ "${IFACE}" = "lo" ] || [ "${IFACE}" = "lo0" ]; then
704 if [ "$1" != "null" ]; then
705 config_0="127.0.0.1/8"
706 config_index=1
707 fi
708 else
709 # 没写配置 → 默认 DHCP,并给出警告
710 if [ -z "$1" ]; then
711 ewarn "config_${IFVAR} not specified; defaulting to DHCP"
712 config_0="dhcp"
713 config_index=1
714 fi
715 fi
716
717
718 # 把每条配置存入 config_<n> 变量(伪数组),
719 # 用变量而非真实数组是为了让模块可以动态修改配置
720 for cmd; do
721 eval config_${config_index}="'${cmd}'"
722 : $(( config_index += 1 ))
723 done
724 # Terminate the list
725 eval config_${config_index}= # 空字符串作为结束标志
726
727 # 同样处理 fallback 列表
728 config_index=0
729 for cmd in ${fallback}; do
730 eval fallback_${config_index}="'${cmd}'"
731 : $(( config_index += 1 ))
732 done
733 # Terminate the list
734 eval fallback_${config_index}=
735
736 # 置为 -1 而不是 0:调用方使用前先 +1,
737 # 这样模块即使不重置索引也能正常工作
738 config_index=-1
739}
740
741# ====================================================================
742# 供模块/用户钩子调用的辅助函数
743# ====================================================================
744
745# _run_if CMD [IFACE] —— 在"指定的另一个接口"上下文里执行 CMD,
746# 同时保存/恢复当前的 IFACE/IFVAR,防止互相覆盖
747_run_if()
748{
749 local cmd=$1 iface=$2 ifr=${IFACE} ifv=${IFVAR}
750 # Ensure that we don't stamp on real values
751 local IFACE='' IFVAR=''
752 shift
753 if [ -n "${iface}" ]; then
754 IFACE="${iface}"
755 [ "${iface}" != "${ifr}" ] && IFVAR=$(shell_var "${IFACE}")
756 else
757 IFACE=${ifr}
758 IFVAR=${ifv}
759 fi
760 ${cmd}
761}
762# 以下四个是模块常用接口:可以操作别的接口(如网桥先 up 成员口)
763interface_exists()
764{
765 _run_if _exists "$@"
766}
767interface_up()
768{
769 _run_if _up "$@"
770}
771interface_down()
772{
773 _run_if _down "$@"
774}
775# 接口类型记录/查询(存入 service 的 kv 存储,跨函数调用持久)
776set_interface_type()
777{
778 service_set_value iface_type "$@"
779}
780get_interface_type()
781{
782 ( RC_SVCNAME="net.$IFACE" service_get_value iface_type )
783}
784is_interface_type()
785{
786 [ "$(get_interface_type)" = "$1" ]
787}
788
789# ====================================================================
790# start() —— 启动接口(OpenRC 服务入口)
791# ====================================================================
792start()
793{
794 local IFACE=''
795 local IFVAR=''
796 local oneworked=false # 是否至少有一条配置成功
797 local fallback=false # 是否正在使用 fallback 配置
798 local module=''
799 local cmd=''
800 local our_metric=''
801 local metric=0
802 local _up_before_preup=''
803 IFACE=$(get_interface)
804 IFVAR=$(shell_var "${IFACE}")
805 # up_before_preup_<IFVAR>:执行 preup 钩子前是否先 ifconfig up 接口
806 eval _up_before_preup="\$up_before_preup_${IFVAR}"
807 # shellcheck disable=SC2154
808 [ -z "${_up_before_preup}" ] && _up_before_preup=$up_before_preup
809
810 einfo "Bringing up interface ${IFACE}"
811 eindent # 输出缩进一级
812
813 # 加载模块(若上层流程尚未加载)
814 if [ -z "${MODULES}" ]; then
815 local MODULES=
816 _load_modules true
817 fi
818
819 # 阶段 1:各模块的 <mod>_pre_up(如创建 tun/tap 设备、vlan 接口)
820 for module in ${MODULES}; do
821 if [ "$(command -v "${module}_pre_up")" = "${module}_pre_up" ]; then
822 ${module}_pre_up || exit $?
823 fi
824 done
825
826 # 阶段 2:用户自定义 preup() 钩子
827 # 在 preup 前后各 up 一次接口:先 up 是为了 preup 里能操作接口;
828 # preup 后再 up 是防止用户在 preup 里误把它 down 掉
829 if [ "$(command -v preup)" = "preup" ]; then
830 yesno "${_up_before_preup:-yes}" && _up 2>/dev/null
831 ebegin "Running preup"
832 eindent
833 preup || return 1
834 eoutdent
835 fi
836
837 _up 2>/dev/null # 正式把接口置为 UP
838
839 # 阶段 3:各模块 <mod>_pre_start(如启动 wpa_supplicant)
840 for module in ${MODULES}; do
841 if [ "$(command -v "${module}_pre_start")" = "${module}_pre_start" ]; then
842 ${module}_pre_start || exit $?
843 fi
844 done
845
846 # 阶段 4:确认接口真实存在(可能 pre_start 刚创建了它,或等待 USB 设备出现)
847 if ! _wait_for_presence; then
848 eerror "ERROR: interface ${IFACE} does not exist"
849 eerror "Ensure that you have loaded the correct kernel module for your hardware"
850 return 1
851 fi
852
853 # 阶段 5:等待载波。若系统有 devd(热插拔守护)在跑,
854 # 没有载波也不算致命——把服务标记为 inactive,等插线后 devd 会重新触发启动
855 if ! _wait_for_carrier; then
856 if service_started devd; then
857 ewarn "no carrier, but devd will start us when we have one"
858 mark_service_inactive "${RC_SVCNAME}"
859 else
860 eerror "no carrier"
861 fi
862 return 1
863 fi
864
865 # 阶段 6:加载配置(config_<IFVAR> → config_0, config_1, ...)
866 local config='' config_index=''
867 _load_config
868 config_index=0
869
870 # 计算路由 metric:用户显式配置优先;
871 # 否则非 lo 接口用接口索引号(ifindex),保证多接口默认路由有确定优先级
872 eval our_metric=\$metric_${IFVAR}
873 if [ -n "${our_metric}" ]; then
874 metric=${our_metric}
875 elif [ "${IFACE}" != "lo" ] && [ "${IFACE}" != "lo0" ]; then
876 : $(( metric += $(_ifindex) ))
877 fi
878
879 # 阶段 7:逐条执行配置
880 while true; do
881 eval config=\$config_${config_index}
882 [ -z "${config}" ] && break # 空值 = 结束
883
884 set -- ${config}
885 if [ "$1" != "null" ] && [ "$1" != "noop" ]; then
886 ebegin "$1" # 打印 " * <config> ..."
887 fi
888 eindent
889 case "$1" in
890 noop)
891 # noop:只检查接口是否已有地址(用于"别的工具已配好"的场景)
892 if [ -n "$(_get_inet_address)" ]; then
893 oneworked=true
894 break
895 fi
896 ;;
897 null) :;; # null:什么都不做
898 [0-9]*|*:*)
899 # 以数字开头 → IPv4/CIDR;含冒号 → IPv6:直接添加静态地址
900 _add_address ${config};;
901 *)
902 # 其他关键字(如 dhcp、pppoe)→ 调用虚拟能力函数 <关键字>_start
903 # (即 _load_modules 里包装好的,转发到具体模块)
904 if [ "$(command -v "${config}_start")" = "${config}_start" ]; then
905 "${config}"_start
906 else
907 eerror "nothing provides \`${config}'"
908 fi
909 ;;
910 esac
911 # ---- 失败处理与 fallback ----
912 if eend $?; then
913 oneworked=true
914 else
915 eval config=\$fallback_${config_index}
916 if [ -n "${config}" ]; then
917 fallback=true
918 eoutdent
919 ewarn "Trying fallback configuration ${config}"
920 eindent
921 # 用 fallback 配置替换当前条目,索引 -1 使其下一轮重新执行
922 eval config_${config_index}=\$config
923 unset fallback_${config_index}
924 : $(( config_index -= 1 ))
925 fi
926 fi
927 eoutdent
928 : $(( config_index += 1 ))
929 done
930
931 # 一条配置都没成功 → 执行 failup 钩子(若定义)后返回失败
932 if ! ${oneworked}; then
933 if [ "$(command -v failup)" = "failup" ]; then
934 ebegin "Running failup"
935 eindent
936 failup
937 eoutdent
938 fi
939 return 1
940 fi
941
942 # 阶段 8:添加路由
943 # 若使用了 fallback 配置,优先读 fallback_routes_<IFVAR>
944 local first=true routes=
945 if ${fallback}; then
946 routes="$(_get_array "fallback_routes_${IFVAR}")"
947 fi
948 if [ -z "${routes}" ]; then
949 routes="$(_get_array "routes_${IFVAR}")"
950 fi
951 # lo 接口自动补 127.0.0.0/8 的本地路由
952 if [ "${IFACE}" = "lo" ] || [ "${IFACE}" = "lo0" ]; then
953 if [ "${config_0}" != "null" ]; then
954 routes="127.0.0.0/8 via 127.0.0.1
955${routes}"
956 fi
957 fi
958
959 service_set_value "nodev_routes" "" # 清空"非本设备路由"记录(供后续查询)
960
961 local OIFS="${IFS}" SIFS="${IFS-y}"
962 local IFS="$__IFS" # 路由按行拆分
963 local cmd_head fam
964 for cmd in ${routes}; do
965 unset IFS # 行内再按空格分词
966 if ${first}; then
967 first=false
968 einfo "Adding routes"
969 fi
970
971 # 行首可写 "-6 "/"-4 " 强制指定地址族
972 case ${cmd} in
973 -6" "*) fam="-6"; cmd=${cmd#-6 };;
974 -4" "*) fam="-4"; cmd=${cmd#-4 };;
975 esac
976
977 # 识别特殊路由类型关键字(blackhole/prohibit/throw/unreachable)
978 cmd_head=
979 case ${cmd%% *} in
980 blackhole|prohibit|throw|unreachable) cmd_head="${cmd_head} ${cmd%% *}"; cmd=${cmd#* };;
981 esac
982
983 eindent
984 ebegin ${cmd_head} ${cmd}
985 # 用户没写 -net/-host 时,根据目标形式自动推断:
986 # 带 netmask 关键字 / 带前缀长度的 IPv4 / default → -net
987 # IPv4/32 或 IPv6/128 → -host
988 # 其余裸 IP → -host
989 case ${cmd} in
990 -net\ *|-host\ *);;
991 *\ netmask\ *) cmd="-net ${cmd}";;
992 *.*.*.*/32*) cmd="-host ${cmd}";;
993 *.*.*.*/*|0.0.0.0|0.0.0.0\ *) cmd="-net ${cmd}";;
994 default|default\ *) cmd="-net ${cmd}";;
995 *:*/128*) cmd="-host ${cmd}";;
996 *:*/*) cmd="-net ${cmd}";;
997 *) cmd="-host ${cmd}";;
998 esac
999 _add_route ${fam} ${cmd_head} ${cmd}
1000 eend $?
1001 eoutdent
1002 done
1003 # 恢复 IFS 原状
1004 if [ "${SIFS}" = "y" ]; then
1005 unset IFS
1006 else
1007 IFS="${OIFS}"
1008 fi
1009
1010 # 阶段 9:各模块 <mod>_post_start(如启动防火墙规则、通告)
1011 for module in ${MODULES}; do
1012 if [ "$(command -v "${module}_post_start")" = "${module}_post_start" ]; then
1013 ${module}_post_start || exit $?
1014 fi
1015 done
1016
1017 # 阶段 10:用户 postup() 钩子
1018 if [ "$(command -v postup)" = "postup" ]; then
1019 ebegin "Running postup"
1020 eindent
1021 postup
1022 eoutdent
1023 fi
1024
1025 return 0
1026}
1027
1028# ====================================================================
1029# stop() —— 停止接口(OpenRC 服务入口)
1030# ====================================================================
1031stop()
1032{
1033 # 系统关机时不真正关闭网络(根文件系统可能挂在 NFS 上等场景);
1034 # 不使用 noshutdown 关键字是为了从单用户模式切回多用户时能被正确重启。
1035 # keep_network=NO 可覆盖此行为。
1036 yesno ${keep_network:-YES} && yesno $RC_GOINGDOWN && return 0
1037
1038 local IFACE='' module=''
1039 local IFVAR=''
1040 IFACE=$(get_interface)
1041 IFVAR=$(shell_var "${IFACE}")
1042
1043 einfo "Bringing down interface ${IFACE}"
1044 eindent
1045
1046 # 停止模式加载模块(列表会被反转)
1047 if [ -z "${MODULES}" ]; then
1048 local MODULES=''
1049 _load_modules false
1050 fi
1051
1052 # 用户 predown() 钩子优先;
1053 # 未定义钩子时做安全检查:根分区是网络文件系统则禁止停网
1054 if [ "$(command -v predown)" = "predown" ]; then
1055 ebegin "Running predown"
1056 eindent
1057 predown || return 1
1058 eoutdent
1059 else
1060 if is_net_fs /; then
1061 eerror "root filesystem is network mounted -- can't stop ${IFACE}"
1062 return 1
1063 fi
1064 fi
1065
1066 # 各模块 <mod>_pre_stop(如断开 wpa_supplicant 前先通知)
1067 for module in ${MODULES}; do
1068 if [ "$(command -v "${module}_pre_stop")" = "${module}_pre_stop" ]; then
1069 ${module}_pre_stop || exit $?
1070 fi
1071 done
1072
1073 # 各模块 <mod>_stop(停 dhcpcd、kill wpa_supplicant 等)
1074 for module in ${MODULES}; do
1075 if [ "$(command -v "${module}_stop")" = "${module}_stop" ]; then
1076 ${module}_stop
1077 fi
1078 done
1079
1080 # 删除接口上的所有地址(仅当接口仍存在;
1081 # PPP 后台模式除外——"demand" 按需拨号时地址由 pppd 自己管理)
1082 if _exists; then
1083 # PPP can manage it's own addresses when IN_BACKGROUND
1084 # Important in case "demand" set on the ppp link
1085 if ! (yesno ${IN_BACKGROUND} && is_ppp) ; then
1086 _delete_addresses "${IFACE}"
1087 fi
1088 fi
1089
1090 # 各模块 <mod>_post_stop
1091 for module in ${MODULES}; do
1092 if [ "$(command -v "${module}_post_stop")" = "${module}_post_stop" ]; then
1093 ${module}_post_stop
1094 fi
1095 done
1096
1097 # 非后台模式且非回环口:把接口 DOWN 掉
1098 # 可用 ifdown_<IFVAR>=no 或全局 ifdown=no 禁止
1099 if ! yesno ${IN_BACKGROUND} && \
1100 [ "${IFACE}" != "lo" ] && [ "${IFACE}" != "lo0" ]; then
1101 eval module=\$ifdown_${IFVAR}
1102 module=${module:-${ifdown:-YES}}
1103 yesno ${module} && _down 2>/dev/null
1104 fi
1105
1106 # 通知 resolvconf 删除该接口注册的 DNS 配置
1107 command -v resolvconf >/dev/null && resolvconf -d "${IFACE}" 2>/dev/null
1108
1109 # 用户 postdown() 钩子
1110 if [ "$(command -v "postdown")" = "postdown" ]; then
1111 ebegin "Running postdown"
1112 eindent
1113 postdown
1114 eoutdent
1115 fi
1116
1117 return 0
1118}接口 #
服务脚本 #
/etc/init.d/net.lo 是 netifrc 自带的服务脚本(通用模板),
当你需要管理其它网络接口,只需创建一个指向 net.lo 的符号链接:
# ln -s /etc/init.d/net.lo /etc/init.d/net.<interface_name>
net.<interface_name> 运行时能从服务名称中获取接口名称:
SHDIR="/lib/netifrc/sh" # 脚本路径
MODULESDIR="/lib/netifrc/net" # 模块路径
if [ -f "$SHDIR/functions.sh" ]; then
. "$SHDIR/functions.sh" # 加载脚本
else
echo "$SHDIR/functions.sh missing. Exiting"
exit 1
fi
start() {
...
IFACE=$(get_interface) # 获取接口名称
...
}get_interface() {
case $INIT in
openrc)
printf ${RC_SVCNAME#*.};; # 打印接口名称
systemd)
printf ${RC_IFACE};;
*)
eerror "Init system not supported. Aborting"
exit 1;;
esac
}
RC_SVCNAME就是服务的名称,是 OpenRC 在执行服务脚本时自动设置的环境变量。
配置文件 #
modules_wlp4s0="wpa_supplicant dhcpcd"
config_wlp4s0="dhcp"modules_<interface_name> | 指定优先使用的模块,模块路径在 /lib/netifrc/net。 |
config_<interface_name> | 网络接口配置,可以是 dhcp、null、192.168.1.10/24(静态 IP)等。 |
禁用模块,在模块前加 !:
modules_wlp4s0="!ifconfig"查看 net 示例配置文件(包含详细说明):
$ less /usr/share/doc/netifrc-*/net.example.bz2
模块 #
net.lo 会遍历 /lib/netifrc/net 下的所有模块,并执行其 depend 函数来建立顺序关系。
以 wpa_supplicant 和 iwconfig 模块为例:
wpa_supplicant_depend()
{
after macnet plug
before interface
provide wireless
# 比起 iwconfig 我们更偏好当前模块
after iwconfig
}
...iwconfig_depend()
{
program iwconfig
after plug
before interface
provide wireless
}
...| 语句 | 含义 |
|---|---|
program iwconfig | 只有当系统中存在 iwconfig 二进制文件时,此模块才生效。 |
provide wireless | 表示提供 wireless 功能。 |
after plug | 当前模块排在 plug 模块后面。 |
before interface | 当前模块排在 interface 模块前面。 |
after/before表示的是顺序关系,而不是依赖关系,只影响模块的排序结果。对于provide相同功能的模块,排在后面的模块会优先被使用。
查看提供功能的模块:
$ cd /lib/netifrc/net
$ grep -w provide * | sort -k3
dhclient.sh: provide dhcp
dhcpcd.sh: provide dhcp
udhcpc.sh: provide dhcp
dhclientv6.sh: provide dhcpv6
ifconfig.sh: provide interface
iproute2.sh: provide interface
ipppd.sh: provide isdn
ifplugd.sh: provide plug
netplugd.sh: provide plug
pppd.sh: provide ppp
iwconfig.sh: provide wireless
iwd.sh: provide wireless
iw.sh: provide wireless
wpa_supplicant.sh: provide wireless
加载顺序 #
简化了下 net.lo 脚本,方便查看模块排序:
1#!/usr/bin/sh
2
3SHDIR="/lib/netifrc/sh"
4MODULESDIR="/lib/netifrc/net"
5. "$SHDIR/functions.sh"
6MODULESLIST=./nettree
7IFACE=wlp4s0
8IFVAR=$IFACE
9
10_program_available()
11{
12 [ -z "$1" ] && return 0
13 local x=
14 for x; do
15 case "${x}" in
16 /*) [ -x "${x}" ] && break;;
17 *) type "${x}" >/dev/null 2>&1 && break;;
18 esac
19 x=
20 done
21 [ -n "${x}" ] && echo $x && return 0
22 return 1
23}
24
25_gen_module_list()
26{
27 local x='' f='' force="$1"
28 if ! ${force} ; then
29 if [ -s "${MODULESLIST}" ] && [ "${MODULESLIST}" -nt /proc/$$/status ]; then
30 echo "Discarding cached module list ($MODULESLIST) as it's newer current time!"
31 elif [ -s "${MODULESLIST}" ] && [ "${MODULESLIST}" -nt "${MODULESDIR}" ]; then
32 local update=false
33 for x in "${MODULESDIR}"/*.sh; do
34 [ -e "${x}" ] || continue
35 if [ "${x}" -nt "${MODULESLIST}" ]; then
36 update=true
37 break
38 fi
39 done
40 ${update} || return 0
41 fi
42 fi
43
44 # Run in a subshell to protect the main script
45 (
46 after() {
47 eval ${MODULE}_after="\"\${${MODULE}_after}\${${MODULE}_after:+ }$*\""
48 }
49
50 before() {
51 local mod=${MODULE}
52 local MODULE=
53 for MODULE; do
54 after "${mod}"
55 done
56 }
57
58 program() {
59 if [ "$1" = "start" ] || [ "$1" = "stop" ]; then
60 local s="$1"
61 shift
62 eval ${MODULE}_program_${s}="\"\${${MODULE}_program_${s}}\${${MODULE}_program_${s}:+ }$*\""
63 else
64 eval ${MODULE}_program="\"\${${MODULE}_program}\${${MODULE}_program:+ }$*\""
65 fi
66 }
67
68 provide() {
69 eval ${MODULE}_provide="\"\${${MODULE}_provide}\${${MODULE}_provide:+ }$*\""
70 local x
71 for x in "$@"; do
72 eval ${x}_providedby="\"\${${MODULE}_providedby}\${${MODULE}_providedby:+ }${MODULE}\""
73 done
74 }
75
76 for MODULE in "${MODULESDIR}"/*.sh; do
77 sh -n "${MODULE}" || continue
78 # shellcheck disable=SC1090
79 . "${MODULE}" || continue
80 MODULE=${MODULE#${MODULESDIR}/}
81 MODULE=${MODULE%.sh}
82 eval "${MODULE}_depend"
83 MODULES="${MODULES} ${MODULE}"
84 done
85
86 VISITED=
87 SORTED=
88 visit() {
89 case " ${VISITED} " in
90 *" $1 "*) return;;
91 esac
92 VISITED="${VISITED} $1"
93
94 eval AFTER=\$${1}_after
95 for MODULE1 in ${AFTER}; do
96 eval PROVIDEDBY=\$${MODULE1}_providedby
97 if [ -n "${PROVIDEDBY}" ]; then
98 for MODULE2 in ${PROVIDEDBY}; do
99 visit "${MODULE2}"
100 done
101 else
102 visit "${MODULE1}"
103 fi
104 done
105
106 eval PROVIDE=\$${1}_provide
107 for MODULE in ${PROVIDE}; do
108 visit "${MODULE}"
109 done
110
111 eval PROVIDEDBY=\$${1}_providedby
112 [ -z "${PROVIDEDBY}" ] && SORTED="${SORTED} $1"
113 }
114
115 for MODULE in ${MODULES}; do
116 visit "${MODULE}"
117 done
118
119 echo -e "\n按顺序关系排序后的模块列表:"
120 for mod in ${SORTED}; do
121 echo -e "\t$mod"
122 done
123
124 # Create atomically
125 TMPMODULESLIST=${MODULESLIST}.$$
126 printf "" > "${TMPMODULESLIST}"
127 i=0
128 for MODULE in ${SORTED}; do
129 eval PROGRAM=\$${MODULE}_program
130 eval PROGRAM_START=\$${MODULE}_program_start
131 eval PROGRAM_STOP=\$${MODULE}_program_stop
132 eval PROVIDE=\$${MODULE}_provide
133 echo "module_${i}='${MODULE}'"
134 echo "module_${i}_program='${PROGRAM}'"
135 echo "module_${i}_program_start='${PROGRAM_START}'"
136 echo "module_${i}_program_stop='${PROGRAM_STOP}'"
137 echo "module_${i}_provide='${PROVIDE}'"
138 : $(( i += 1 ))
139 done >> "${TMPMODULESLIST}"
140 echo "module_${i}=" >> "${TMPMODULESLIST}"
141 mv -f "${TMPMODULESLIST}" "${MODULESLIST}"
142 )
143
144 return 0
145}
146
147_load_modules()
148{
149 local starting=$1 mymods=
150
151 _gen_module_list true
152 . "${MODULESLIST}"
153
154 MODULES=
155 if [ "${IFACE}" != "lo" ] && [ "${IFACE}" != "lo0" ]; then
156 eval mymods=\$modules_${IFVAR}
157 # shellcheck disable=SC2154
158 [ -z "${mymods}" ] && mymods=${modules}
159 fi
160
161 local i=-1 x='' mod='' f='' provides=''
162 echo -e "\n跳过的模块:"
163 while true; do
164 : $(( i += 1 ))
165 eval mod=\$module_${i}
166 [ -z "${mod}" ] && break
167 [ -e "${MODULESDIR}/${mod}.sh" ] || printf "\t%-20s:模块不存在 %s\n" $mod "${MODULESDIR}/${mod}.sh"
168 [ -e "${MODULESDIR}/${mod}.sh" ] || continue
169
170 eval set -- \$module_${i}_program
171 if [ -n "$1" ]; then
172 if ! _program_available "$@" >/dev/null; then
173 printf "\t%-20s:缺失可执行程序 %s\n" $mod "$*"
174 continue
175 fi
176 fi
177 if ${starting}; then
178 eval set -- \$module_${i}_program_start
179 else
180 eval set -- \$module_${i}_program_stop
181 fi
182 if [ -n "$1" ]; then
183 if ! _program_available "$@" >/dev/null; then
184 printf "\t%-20s:缺失可执行程序 %s\n" $mod "$*"
185 continue
186 fi
187 fi
188
189 eval provides=\$module_${i}_provide
190 if ${starting}; then
191 case " ${mymods} " in
192 *" !${mod} "*) continue;;
193 *" !${provides} "*) [ -n "${provides}" ] && continue;;
194 esac
195 fi
196 MODULES="${MODULES}${MODULES:+ }${mod}"
197
198 # Now load and wrap our functions
199 # shellcheck disable=SC1090
200 if ! . "${MODULESDIR}/${mod}.sh"; then
201 echo "${RC_SVCNAME}: error loading module \`${mod}'"
202 exit 1
203 fi
204
205 [ -z "${provides}" ] && continue
206
207 # Wrap our provides
208 local f=
209 for f in pre_start start post_start; do
210 inner=$(command -v "${mod}_${f}")
211 eval "${provides}_${f}() { [ '${inner}' = '${mod}_${f}' ] || return 0; ${mod}_${f} \"\$@\"; }"
212 done
213
214 eval module_${mod}_provides="${provides}"
215 eval module_${provides}_providedby="${mod}"
216 done
217
218 # Wrap our preferred modules
219 for mod in ${mymods}; do
220 case " ${MODULES} " in
221 *" ${mod} "*)
222 eval x=\$module_${mod}_provides
223 [ -z "${x}" ] && continue
224 for f in pre_start start post_start; do
225 inner=$(command -v "${mod}_${f}")
226 eval "${x}_${f}() { [ '${inner}' = '${mod}_${f}' ] || return 0; ${mod}_${f} \"\$@\"; }"
227 done
228 eval module_${x}_providedby="${mod}"
229 ;;
230 esac
231 done
232
233 # Finally remove any duplicated provides from our list if we're starting
234 # Otherwise reverse the list
235 local LIST="${MODULES}" p=
236 MODULES=
237 if ${starting}; then
238 for mod in ${LIST}; do
239 eval x=\$module_${mod}_provides
240 if [ -n "${x}" ]; then
241 eval p=\$module_${x}_providedby
242 [ "${mod}" != "${p}" ] && printf "\t%-20s:功能重复 %s -> %s\n" ${mod} ${x} ${p} && continue
243 fi
244 MODULES="${MODULES}${MODULES:+ }${mod}"
245 done
246 else
247 for mod in ${LIST}; do
248 MODULES="${mod}${MODULES:+ }${MODULES}"
249 done
250 fi
251
252 echo -e "\n最终的模块列表:"
253
254 for mod in ${MODULES}; do
255 echo -e "\t$mod"
256 done
257}
258
259_load_modules true运行结果:
$ ./load_modules.sh
按顺序关系排序后的模块列表:
adsl
apipa
arping
bonding
br2684ctl
l2tp
tuntap
bridge
ccwgroup
clip
ethtool
dummy
hsr
macvlan
macchanger
macnet
rename
netplugd
ifplugd
ipppd
iwconfig
iwd
iw
qmi
wpa_supplicant
ssidnet
wireguard
ifconfig
iproute2
firewalld
pppd
system
vlan
udhcpc
dhclient
dhclientv6
dhcpcd
ip6rd
ip6to4
ip6token
veth
跳过的模块:
adsl :缺失可执行程序 adsl-start pppoe-start
br2684ctl :缺失可执行程序 br2684ctl
clip :缺失可执行程序 atmsigd
ethtool :缺失可执行程序 ethtool
rename :模块不存在 /lib/netifrc/net/rename.sh
netplugd :缺失可执行程序 netplugd
ifplugd :缺失可执行程序 ifplugd
ipppd :缺失可执行程序 ipppd
iwconfig :缺失可执行程序 iwconfig
iwd :缺失可执行程序 /usr/libexec/iwd
firewalld :缺失可执行程序 firewall-cmd
pppd :缺失可执行程序 pppd
udhcpc :缺失可执行程序 busybox
dhclient :缺失可执行程序 dhclient
dhclientv6 :缺失可执行程序 dhclient
iw :功能重复 wireless -> wpa_supplicant
ifconfig :功能重复 interface -> iproute2
最终的模块列表:
apipa
arping
bonding
l2tp
tuntap
bridge
ccwgroup
dummy
hsr
macvlan
macchanger
macnet
qmi
wpa_supplicant
ssidnet
wireguard
iproute2
system
vlan
dhcpcd
ip6rd
ip6to4
ip6token
veth
模拟 net 配置文件,优先使用 ifconfig 而不是 iproute2:
$ modules_wlp4s0="ifconfig" ./load_modules.sh
...
iproute2 :功能重复 interface -> ifconfig
排序好的模块在 start 函数中会被遍历执行 ${module}_pre_up、${module}_pre_start 等函数,
一些模块需要在 /etc/conf.d/net 中额外配置参数(不需要就不配置,模块会直接返回)。
wpa_supplicant #
配置文件 #
# 允许 wheel 组的用户控制 wpa_supplicant
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=wheel
# 允许 wpa_gui / wpa_cli 可写该文件
update_config=1添加无线网络,名称不能包含中文字符或其它特殊字符,只能是 ascii 字符:
# wpa_passphrase 网络名称 密码 >> /etc/wpa_supplicant/wpa_supplicant.conf
上面命令会在 wpa_supplicant.conf 中添加如下内容:
network={
ssid="wifi1"
psk=6ad077ee70b4541967ee9c0bf0dc902
}如果你添加了多个网络,可以使用 priority 变量设置连接优先级,数字越大,优先级越高:
network={
ssid="wifi1"
psk=6ad077ee70b4541967ee9c0bf0dc90
priority=10
}其它可用的变量见 wpa_supplicant.conf(5) 中的
NETWORK BLOCKS一节。
手动连接网络:
# wpa_supplicant -B -i wlp4s0 -c /etc/wpa_supplicant/wpa_supplicant.conf
获取 IP,可用 dhclient 或 dhcpcd:
# dhclient -i wlp4s0 -v
# dhcpcd wlp4s0
netifrc 配置 #
modules_wlp4s0="wpa_supplicant dhcpcd"
config_wlp4s0="dhcp"添加开机启动:
# ln -s /etc/init.d/net.lo /etc/init.d/net.wlp4s0
# rc-update add net.wlp4s0 default
dhcpd 不用添加,由 net.wlp4s0 启动。
其它 #
wpa_cli 交互式命令行工具 #
直接在终端执行 wpa_cli 命令,即可进入交互模式:
$ wpa_cli -i wlp4s0
Interactive mode
> help # 列出所有指令,及其用法
略...
> list_networks # 打印已添加的网络
network id / ssid / bssid / flags
0 wifi1 any
1 wifi2 any [CURRENT]
> scan # 扫描网络
OK
<3>CTRL-EVENT-SCAN-STARTED
<3>CTRL-EVENT-SCAN-RESULTS
> scan_results # 查看扫描结果
1c:16:a1:12:1d:a8 5765 -27 [WPA-PSK-CCMP][WPA2-PSK-CCMP][ESS] wifi1
10:1b:13:d4:14:6c 5200 -62 [WPA-PSK-CCMP][WPA2-PSK-CCMP][ESS] wifi2
1c:16:a7:12:4d:a6 2412 -23 [WPA-PSK-CCMP][WPA2-PSK-CCMP][ESS] wifi3
> add_network # 添加网络
2
<3>CTRL-EVENT-NETWORK-ADDED 2
> set_network 2 ssid "wifi3" # 设置网络2的ssid (配置文件中 network 的 ssid 变量)
> set_network 2 psk "passphrase" # 设置网络2的密码
> enable_network 2 # 使能网络2
> save_config # 保存到配置文件
> select_network 2 # 连接到网络2
非交互模式,直接在命令后面跟指令,例如:
$ wpa_cli -i wlp4s0 list_networks
动作脚本 #
Gentoo 上的 wpa_supplicant 附带了一个动作脚本 /etc/wpa_supplicant/wpa_cli.sh,
wpa_cli 会以守护进程方式监听 wpa_supplicant 发出的网络事件,当事件发生时自动执行该脚本。
可以在模块中看到相关代码,通过 -a 参数指定动作脚本:
local actfile=/etc/wpa_supplicant/wpa_cli.sh
...
ebegin "Starting wpa_cli on" "${IFACE}"
start-stop-daemon --start --exec "${wpac}" \
--pidfile "/run/wpa_cli-${IFACE}.pid" \
-- ${cliopts} -a "${actfile}" -p "${ctrl_dir}" -i "${IFACE}" \
-P "/run/wpa_cli-${IFACE}.pid" -B动作脚本如下:
#!/bin/sh
# Copyright 1999-2011 Gentoo Foundation
# Written by Roy Marples <uberlord@gentoo.org>
# Distributed under the terms of the GNU General Public License v2
# Alternatively, this file may be distributed under the terms of the BSD License
if [ -z "$1" -o -z "$2" ]; then
logger -t wpa_cli "Insufficient parameters"
exit 1
fi
INTERFACE="$1"
ACTION="$2"
# Note, the below action must NOT mark the interface down via ifconfig, ip or
# similar. Addresses can be removed, changed and daemons can be stopped, but
# the interface must remain up for wpa_supplicant to work.
if [ -f /etc/gentoo-release ]; then
EXEC="/etc/init.d/net.${INTERFACE} --quiet"
else
logger -t wpa_cli "I don't know what to do with this distro!"
exit 1
fi
case ${ACTION} in
CONNECTED)
EXEC="${EXEC} start"
;;
DISCONNECTED)
EXEC="${EXEC} --nodeps stop"
;;
*)
logger -t wpa_cli "Unknown action ${ACTION}"
exit 1
;;
esac
# ${EXEC} can use ${IN_BACKGROUND} so that it knows that the user isn't
# stopping the interface and a background process - like wpa_cli - is.
export IN_BACKGROUND=true
logger -t wpa_cli "interface ${INTERFACE} ${ACTION}"
${EXEC} || logger -t wpa_cli "executing '${EXEC}' failed"wpa_cli 会根据事件类型(如 CONNECTED 或 DISCONNECTED),
将接口名和动作名作为参数传给脚本。
可以在 wpa_cli 交互模式下看到事件提示,如:
$ wpa_cli
> disable_network 0
OK
<3>CTRL-EVENT-DISCONNECTED bssid=3c:06:a7:a2:4d:a8 reason=3 locally_generated=1
ssid 中文字符无法显示 #
$ wpa_cli -i wlp4s0 scan
$ wpa_cli -i wlp4s0 scan_result | sed 's@\\@\\\\@g' | xargs -L1 echo -e
或者使用 iw 命令扫描:
# iw dev wlp4s0 scan | grep -i ssid | sed 's@\\@\\\\@g' | xargs -L1 echo -e
找到对应的中文 ssid 后,在 wpa_cli 中按之前的步骤操作。
注意:不能直接在
wpa_supplicant.conf中添加中文字符的ssid。