我之前以为PPOE上网,网线直插DHCP,或者手动指定IP上网。滚出来了才知道还有另外一种上网方式DHCP+VLAN上网。
飞牛并没有这种上网方式,所以只能手动了。
现在代码贴出来了。你们自己看,自己研究。
#!/usr/bin/env bash
set -euo pipefail
SERVICE_NAME="fnnas-vlan-dhcp.service"
CONFIG_FILE="/etc/fnnas-vlan-dhcp.conf"
HELPER_FILE="/usr/local/sbin/fnnas-vlan-dhcp"
SYSTEMD_FILE="/etc/systemd/system/${SERVICE_NAME}"
need_root() {
if [[ "${EUID}" -ne 0 ]]; then
echo "Please run as root: sudo bash $0"
exit 1
fi
}
has_cmd() {
command -v "$1" >/dev/null 2>&1
}
show_current_status() {
echo "===== systemd status ====="
systemctl is-enabled "${SERVICE_NAME}" 2>/dev/null || true
systemctl status "${SERVICE_NAME}" --no-pager 2>/dev/null || true
echo
echo "===== saved config ====="
if [[ -f "${CONFIG_FILE}" ]]; then
cat "${CONFIG_FILE}"
else
echo "${CONFIG_FILE} not found"
fi
echo
echo "===== current VLAN interface ====="
if [[ -f "${CONFIG_FILE}" ]]; then
# shellcheck disable=SC1090
source "${CONFIG_FILE}"
ip -d link show "${VLAN_IFACE}" 2>/dev/null || true
ip addr show "${VLAN_IFACE}" 2>/dev/null || true
ip route show dev "${VLAN_IFACE}" 2>/dev/null || true
fi
echo
echo "===== boot logs ====="
journalctl -u "${SERVICE_NAME}" -b --no-pager 2>/dev/null || true
}
remove_installation() {
echo "Stopping and removing ${SERVICE_NAME} ..."
systemctl disable --now "${SERVICE_NAME}" >/dev/null 2>&1 || true
if [[ -x "${HELPER_FILE}" ]]; then
"${HELPER_FILE}" stop >/dev/null 2>&1 || true
fi
rm -f "${SYSTEMD_FILE}" "${HELPER_FILE}" "${CONFIG_FILE}"
systemctl daemon-reload
echo "Removed."
}
collect_interfaces() {
local iface path
IFACES=()
for path in /sys/class/net/*; do
iface="${path##*/}"
[[ "${iface}" == "lo" ]] && continue
[[ "${iface}" == *.* ]] && continue
[[ "${iface}" == docker* ]] && continue
[[ "${iface}" == br-* ]] && continue
[[ "${iface}" == veth* ]] && continue
[[ "${iface}" == virbr* ]] && continue
[[ "${iface}" == tailscale* ]] && continue
if [[ -e "${path}/device" ]]; then
IFACES+=("${iface}")
fi
done
if [[ "${#IFACES[@]}" -eq 0 ]]; then
for path in /sys/class/net/*; do
iface="${path##*/}"
[[ "${iface}" == "lo" ]] && continue
[[ "${iface}" == *.* ]] && continue
IFACES+=("${iface}")
done
fi
IFS=$'\n' IFACES=($(sort <<<"${IFACES[*]}"))
unset IFS
}
print_interfaces() {
local i iface mac state carrier ipv4
echo "Detected ${#IFACES[@]} selectable network interface(s):"
echo
for i in "${!IFACES[@]}"; do
iface="${IFACES[$i]}"
mac="$(cat "/sys/class/net/${iface}/address" 2>/dev/null || echo unknown)"
state="$(cat "/sys/class/net/${iface}/operstate" 2>/dev/null || echo unknown)"
carrier="$(cat "/sys/class/net/${iface}/carrier" 2>/dev/null || echo unknown)"
ipv4="$(ip -4 -o addr show dev "${iface}" 2>/dev/null | awk '{print $4}' | paste -sd ',' -)"
[[ -n "${ipv4}" ]] || ipv4="-"
printf "%2d) %-18s MAC %-17s state %-8s carrier %-7s IPv4 %s\n" \
"$((i + 1))" "${iface}" "${mac}" "${state}" "${carrier}" "${ipv4}"
done
echo
}
choose_interface() {
local index
while true; do
read -r -p "Choose interface number [1-${#IFACES[@]}]: " index
if [[ "${index}" =~ ^[0-9]+$ ]] && ((index >= 1 && index <= ${#IFACES[@]})); then
SELECTED_IFACE="${IFACES[$((index - 1))]}"
return
fi
echo "Invalid input. Please try again."
done
}
choose_vlan_id() {
local vlan
while true; do
read -r -p "Enter VLAN ID [1-4094], for example 832: " vlan
if [[ "${vlan}" =~ ^[0-9]+$ ]] && ((vlan >= 1 && vlan <= 4094)); then
SELECTED_VLAN="${vlan}"
return
fi
echo "Invalid VLAN ID. Please enter a number from 1 to 4094."
done
}
write_config() {
local iface="$1"
local vlan="$2"
local vlan_iface="${iface}.${vlan}"
cat >"${CONFIG_FILE}" <<EOF_CONF
IFACE="${iface}"
VLAN_ID="${vlan}"
VLAN_IFACE="${vlan_iface}"
EOF_CONF
chmod 0644 "${CONFIG_FILE}"
}
write_helper() {
cat >"${HELPER_FILE}" <<'EOF_HELPER'
#!/usr/bin/env bash
set -euo pipefail
CONFIG_FILE="/etc/fnnas-vlan-dhcp.conf"
log() {
echo "[fnnas-vlan-dhcp] $*"
}
has_cmd() {
command -v "$1" >/dev/null 2>&1
}
load_config() {
if [[ ! -f "${CONFIG_FILE}" ]]; then
log "Config file not found: ${CONFIG_FILE}"
exit 1
fi
# shellcheck disable=SC1090
source "${CONFIG_FILE}"
if [[ -z "${IFACE:-}" || -z "${VLAN_ID:-}" || -z "${VLAN_IFACE:-}" ]]; then
log "Config file is incomplete."
exit 1
fi
}
wait_for_interface() {
local i
for i in $(seq 1 30); do
if [[ -e "/sys/class/net/${IFACE}" ]]; then
return 0
fi
sleep 1
done
log "Timed out waiting for interface ${IFACE}."
exit 1
}
start_vlan() {
load_config
wait_for_interface
log "Loading 802.1Q module."
modprobe 8021q >/dev/null 2>&1 || true
log "Bringing up physical interface ${IFACE}."
ip link set "${IFACE}" up
if ! ip link show "${VLAN_IFACE}" >/dev/null 2>&1; then
log "Creating VLAN interface ${VLAN_IFACE} with VLAN ID ${VLAN_ID}."
ip link add link "${IFACE}" name "${VLAN_IFACE}" type vlan id "${VLAN_ID}"
else
log "VLAN interface ${VLAN_IFACE} already exists."
fi
ip link set "${VLAN_IFACE}" up
if has_cmd dhclient; then
log "Getting DHCP address with dhclient."
dhclient -r "${VLAN_IFACE}" >/dev/null 2>&1 || true
if has_cmd timeout; then
timeout 60 dhclient -4 -v -1 "${VLAN_IFACE}"
else
dhclient -4 -v -1 "${VLAN_IFACE}"
fi
elif has_cmd udhcpc; then
log "Getting DHCP address with udhcpc."
pkill -f "udhcpc.*${VLAN_IFACE}" >/dev/null 2>&1 || true
nohup udhcpc -i "${VLAN_IFACE}" -p "/run/udhcpc-${VLAN_IFACE}.pid" \
>>"/var/log/fnnas-vlan-dhcp.log" 2>&1 &
sleep 5
else
log "No DHCP client found. Please install dhclient or udhcpc."
exit 1
fi
log "Current address:"
ip addr show "${VLAN_IFACE}" || true
log "Current routes:"
ip route show dev "${VLAN_IFACE}" || true
}
stop_vlan() {
load_config
log "Releasing DHCP address."
if has_cmd dhclient; then
dhclient -r "${VLAN_IFACE}" >/dev/null 2>&1 || true
fi
if [[ -f "/run/udhcpc-${VLAN_IFACE}.pid" ]]; then
kill "$(cat "/run/udhcpc-${VLAN_IFACE}.pid")" >/dev/null 2>&1 || true
rm -f "/run/udhcpc-${VLAN_IFACE}.pid"
fi
if ip link show "${VLAN_IFACE}" >/dev/null 2>&1; then
log "Deleting VLAN interface ${VLAN_IFACE}."
ip link delete "${VLAN_IFACE}" || true
fi
}
status_vlan() {
load_config
ip -d link show "${VLAN_IFACE}" 2>/dev/null || true
ip addr show "${VLAN_IFACE}" 2>/dev/null || true
ip route show dev "${VLAN_IFACE}" 2>/dev/null || true
}
case "${1:-start}" in
start)
start_vlan
;;
stop)
stop_vlan
;;
restart)
stop_vlan
start_vlan
;;
status)
status_vlan
;;
*)
echo "Usage: $0 {start|stop|restart|status}"
exit 2
;;
esac
EOF_HELPER
chmod 0755 "${HELPER_FILE}"
}
write_systemd_service() {
cat >"${SYSTEMD_FILE}" <<EOF_SERVICE
[Unit]
Description=Create VLAN interface and get DHCP address for fnOS
Wants=systemd-udev-settle.service
After=systemd-udev-settle.service network-pre.target
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=${HELPER_FILE} start
ExecStop=${HELPER_FILE} stop
[Install]
WantedBy=multi-user.target
EOF_SERVICE
chmod 0644 "${SYSTEMD_FILE}"
}
install_vlan_service() {
local confirm vlan_iface
collect_interfaces
if [[ "${#IFACES[@]}" -eq 0 ]]; then
echo "No usable network interfaces were detected."
exit 1
fi
print_interfaces
choose_interface
choose_vlan_id
vlan_iface="${SELECTED_IFACE}.${SELECTED_VLAN}"
echo
echo "The following config will be installed:"
echo "Physical interface: ${SELECTED_IFACE}"
echo "VLAN ID : ${SELECTED_VLAN}"
echo "VLAN interface : ${vlan_iface}"
echo
read -r -p "Continue? [y/N]: " confirm
if [[ ! "${confirm}" =~ ^[Yy]$ ]]; then
echo "Canceled."
exit 0
fi
if systemctl list-unit-files "${SERVICE_NAME}" >/dev/null 2>&1; then
systemctl stop "${SERVICE_NAME}" >/dev/null 2>&1 || true
fi
write_config "${SELECTED_IFACE}" "${SELECTED_VLAN}"
write_helper
write_systemd_service
systemctl daemon-reload
systemctl enable "${SERVICE_NAME}"
echo
echo "Starting ${SERVICE_NAME} and requesting a DHCP address ..."
if systemctl restart "${SERVICE_NAME}"; then
echo
echo "Done. Current status:"
systemctl status "${SERVICE_NAME}" --no-pager || true
echo
"${HELPER_FILE}" status || true
else
echo
echo "Service start failed. Logs:"
journalctl -u "${SERVICE_NAME}" -b --no-pager || true
exit 1
fi
}
main() {
need_root
case "${1:-install}" in
install)
install_vlan_service
;;
status)
show_current_status
;;
remove|uninstall)
remove_installation
;;
*)
echo "Usage: sudo bash $0 [install|status|remove]"
exit 2
;;
esac
}
main "$@"