Hatred's Log Place

DON'T PANIC!

Nov 23, 2008 - 9 minute read -

VLAN в ArchLinux

Волей судеб понадобилось в ArchLinux поднять несколько VLAN. Привычным движением открываю файл /etc/rc.conf на редактирование, прописываю туда: eth2v0001=“eth2.0001 192.168.0.254 netmask 255.255.255.0”

добавляю eth2v0001 в INTERFACES: INTERFACES=(… eth2v0001 …)

и поднимаю интерфейс: /etc/rc.d/network ifup eth2v0001

и… получаю ругань что такого интерфейса не существует.

В чем проблема?.. Иду на http://wiki.archlinux.org и делаю поиск по слову VLAN, нахожу всего одну статью: High Performance Firewall/Nat with iptables and VLANs and iproute2 в которой упоминается программа vconfig из одноименного пакета - именно она нужна для настройки VLAN на сетевых интерфейсах.

Ладно, с этим всё хорошо, но неужели ручками настраивать все VLAN которые потребуются?

Почесав макушку, иду смотреть содержимое файла /etc/rc.d/network и нахожу, что для VLAN там нет вообще ничего.

“Если гора не идет к Магомеду, то Магомед идет к горе”, - подумал я, и краем глаза посмотрел как процедура сделана в Debian Linux. Там в директории /etc/network/if-pre-up.d лежит скрипт vlan который занимается конфигурированием VLAN. В директории /etc/network/if-post-down.d скрипт vlan занимается подчисткой следов после своей работы - сиречь удаляет созданные VLAN.

Поразмыслив, решаю допиливать скрипт настройки сети в Arch.

Для начала ставим нужные пакеты: pacman -S vconfig iproute

Создаём файл /etc/rc.d/network_vlan со следующим содержимым:

#!/bin/bash

. /etc/rc.conf
. /etc/rc.d/functions

# wireless settings
[ -f /etc/conf.d/wireless ] && . /etc/conf.d/wireless
# ethernet bonding settings
[ -f /etc/conf.d/bonding ] && . /etc/conf.d/bonding
# bridge settings
[ -f /etc/conf.d/bridges ] && . /etc/conf.d/bridges
# dhcpcd settings
[ -f /etc/conf.d/dhcpcd ] && . /etc/conf.d/dhcpcd

# Code taked from Debian distribution
vlan_up()
{
    local iface
    local VLANID
    local IF_VLAN_RAW_DEVICE
    iface=$1

    case "$iface" in
        vlan0*)
                vconfig set_name_type VLAN_PLUS_VID > /dev/null 2>&1
                VLANID=`echo $iface|sed "s/vlan0*//"`
                ;;
        vlan*)
                vconfig set_name_type VLAN_PLUS_VID_NO_PAD > /dev/null 2>&1
                VLANID=`echo $iface|sed "s/vlan0*//"`
                ;;
        eth*.0*)
                vconfig set_name_type DEV_PLUS_VID > /dev/null 2>&1
                VLANID=`echo $iface|sed "s/eth[0-9][0-9]*<br/>.0*//g"`
                IF_VLAN_RAW_DEVICE=`echo $iface|sed "s/<br/>(eth[0-9][0-9]*<br/>)<br/>..*/<br/>1/"`
                ;;
        eth*.*)
                vconfig set_name_type DEV_PLUS_VID_NO_PAD > /dev/null 2>&1
                VLANID=`echo $iface|sed "s/eth[0-9][0-9]*<br/>.0*//g"`
                IF_VLAN_RAW_DEVICE=`echo $iface|sed "s/<br/>(eth[0-9][0-9]*<br/>)<br/>..*/<br/>1/"`
                ;;
        *)      return 0
                ;;
    esac

    if [ -n "$IF_VLAN_RAW_DEVICE" ]
    then
        if [ ! -x /usr/sbin/vconfig ]; then
          return 0
        fi

        if ! ip link show dev "$IF_VLAN_RAW_DEVICE" > /dev/null; then
            #echo "$IF_VLAN_RAW_DEVICE does not exist, unable to create $iface"
            return 1
        fi
        ip link set up dev $IF_VLAN_RAW_DEVICE > /dev/null 2>&1
        vconfig add $IF_VLAN_RAW_DEVICE $VLANID > /dev/null 2>&1
    fi

    return 0
}

vlan_down()
{
    local iface
    local IF_VLAN_RAW_DEVICE

    iface=$1

    case "$iface" in
        eth*.0*)
                IF_VLAN_RAW_DEVICE=`echo $IFACE|sed "s/<br/>(eth[0-9][0-9]*<br/>)<br/>..*/<br/>1/"`
                ;;
        eth*.*)
                IF_VLAN_RAW_DEVICE=`echo $IFACE|sed "s/<br/>(eth[0-9][0-9]*<br/>)<br/>..*/<br/>1/"`
                ;;
        *)      return 0
                ;;
    esac

    if [ -z "$IF_VLAN_RAW_DEVICE" ]; then
        return 0
    fi

    if [ ! -x /usr/sbin/vconfig ]; then
	return 0
    fi

    vconfig rem $face > /dev/null 2>&1
}

ifup()
{
	if [ "$1" = "" ]; then
		echo "usage: $0 ifup <interface_name>"
		return 1
	fi

	wi_up $1 || return 1

	eval ifcfg="<br/>$${1}"
	if [ "$ifcfg" = "dhcp" ]; then
		# remove the .pid file if it exists
		/bin/rm -f /var/run/dhcpcd-${1}.pid >/dev/null 2>&1
		/bin/rm -f /var/run/dhcpcd-${1}.cache >/dev/null 2>&1
		/sbin/dhcpcd $DHCPCD_ARGS ${1}
	else
		iface=`echo $ifcfg | awk '{print $1}'`
		vlan_up $iface || return 1

		/sbin/ifconfig $ifcfg
	fi
	return $?
}

wi_up()
{
	eval iwcfg="<br/>$wlan_${1}"
	[ "$iwcfg" = "" ] && return 0

	/usr/sbin/iwconfig $iwcfg
	[[ -z "$WIRELESS_TIMEOUT" ]] && WIRELESS_TIMEOUT=2
	sleep $WIRELESS_TIMEOUT

	bssid=`iwgetid $1 -ra`
	if [[ "$bssid" = "00:00:00:00:00:00" ]]; then
		printhl "Could not associate $1 - try increasing WIRELESS_TIMEOUT and check network is WEP or has no security"
		return 1
	fi
	return 0
}

ifdown()
{
	if [ "$1" = "" ]; then
		echo "usage: $0 ifdown <interface_name>"
		return 1
	fi
	eval ifcfg="<br/>$${1}"
	if [ "$ifcfg" = "dhcp" ]; then
		if [ -f /var/run/dhcpcd-${1}.pid ]; then
			/bin/kill $(cat /var/run/dhcpcd-${1}.pid)
		fi
	fi
	# Always bring the interface itself down
	/sbin/ifconfig ${1} down >/dev/null 2>&1

	iface=`echo $ifcfg | awk '{print $1}'`
	vlan_down $iface || return 1

	return 1
}

iflist()
{
	local iface
	for ifline in ${INTERFACES[@]}; do
		if [ "$ifline" = "${ifline#!}" ]; then
			printf " $ifline:<br/>t"
		else
			printf "$ifline:<br/>t"
		fi
		eval real_ifline=<br/>$${ifline#!}

		iface=`echo $real_ifline | awk '{print $1}'`
		if [ x"$iface" != x"dhcp" ]; then
		    is_run=`ifconfig $iface 2>/dev/null | grep 'inet addr'`
		    if [ -z "$is_run" ]; then
			run='( )'
		    else
			run='(*)'
		    fi
		else
		    run='(?)'
		fi

		echo $real_ifline $run
	done
}

rtup()
{
	if [ "$1" = "" ]; then
		echo "usage: $0 rtup <route_name>"
		return 1
	fi
	eval routecfg="<br/>$${1}"
	if grep -q :: <<< $routecfg; then
			/sbin/route -A inet6 add $routecfg
	else
			/sbin/route add $routecfg
	fi
	return $?
}

rtdown()
{
	if [ "$1" = "" ]; then
		echo "usage: $0 rtdown <route_name>"
		return 1
	fi
	eval routecfg="<br/>$${1}"
	if grep -q :: <<< $routecfg; then
			/sbin/route -A inet6 del $routecfg
	else
			/sbin/route del $routecfg
	fi
	return $?
}

rtlist()
{
	for rtline in ${ROUTES[@]}; do
		if [ "$rtline" = "${rtline#!}" ]; then
			printf " $rtline:<br/>t"
		else
			printf "$rtline:<br/>t"
		fi
		eval real_rtline=<br/>$${rtline#!}
		echo $real_rtline
	done
}

bond_up()
{
	for ifline in ${BOND_INTERFACES[@]}; do
		if [ "$ifline" = "${ifline#!}" ]; then
			eval bondcfg="<br/>$bond_${ifline}"
			/sbin/ifenslave $ifline $bondcfg || error=1
		fi
	done
}

bridge_up()
{
	for br in ${BRIDGE_INTERFACES[@]}; do
		if [ "$br" = "${br#!}" ]; then
			# if the bridge already exists, remove it
			if [ "$(/sbin/ifconfig $br 2>/dev/null)" ]; then
				/sbin/ifconfig $br down
				/usr/sbin/brctl delbr $br
			fi
			/usr/sbin/brctl addbr $br
			eval brifs="<br/>$bridge_${br}"
			for brif in $brifs; do
				if [ "$brif" = "${brif#!}" ]; then
					/usr/sbin/brctl addif $br $brif || error=1
				fi
			done
		fi
	done
}

bridge_down()
{
	for br in ${BRIDGE_INTERFACES[@]}; do
		if [ "$br" = "${br#!}" ]; then
			/usr/sbin/brctl delbr $br
		fi
	done
}


case "$1" in
	start)
		if ! ck_daemon network; then
			echo "Network is already running.  Try 'network restart'"
			exit
		fi

		stat_busy "Starting Network"
		error=0
		# bring up bridge interfaces
		bridge_up
		# bring up ethernet interfaces
		for ifline in ${INTERFACES[@]}; do
			if [ "$ifline" = "${ifline#!}" ]; then
				ifup $ifline || error=1
			fi
		done
		# bring up bond interfaces
		bond_up
		# bring up routes
		for rtline in "${ROUTES[@]}"; do
			if [ "$rtline" = "${rtline#!}" ]; then
				rtup $rtline || error=1
			fi
		done
		if [ $error -eq 0 ]; then
			add_daemon network
			stat_done
		else
			stat_fail
		fi
		;;
	stop)
		#if ck_daemon network; then
		#	echo "Network is not running.  Try 'network start'"
		#	exit
		#fi

		stat_busy "Stopping Network"
		rm_daemon network
		error=0
		for rtline in "${ROUTES[@]}"; do
			if [ "$rtline" = "${rtline#!}" ]; then
				rtdown $rtline || error=1
			fi
		done
		for ifline in ${INTERFACES[@]}; do
			if [ "$ifline" = "${ifline#!}" ]; then
				ifdown $ifline || error=1
			fi
		done
		# bring down bridge interfaces
		bridge_down
		if [ $error -eq 0 ]; then
			stat_done
		else
			stat_fail
		fi
		;;
	restart)
		$0 stop
		/bin/sleep 2
		$0 start
		;;
	hotplug_ifup|ifup|ifdown|iflist|rtup|rtdown|rtlist)
		$1 $2
		;;
	*)
		echo "usage: $0 {start|stop|restart}"
		echo "       $0 {ifup|ifdown|iflist|rtup|rtdown|rtlist}"
esac

# vim: set ts=2 noet:

Он основан на оригинальном скрипте network из пакета initscripts. Кроме поддержки VLAN там подправлен немного вывод iflist который добавляет в конце каждый строки с интерфейсом:

  • (*) - интерфейс поднят
  • ( ) - интерфейс не поднят
  • (?) - dhcp

Т.к. это скрипт основан на оригинальном, имеет смысл предоставить diff между оригиналом и новым файлом:

--- network	2008-09-19 07:26:34.000000000 +1100
@@ -12,6 +12,83 @@
 # dhcpcd settings
 [ -f /etc/conf.d/dhcpcd ] && . /etc/conf.d/dhcpcd

+# Code taked from Debian distribution
+vlan_up()
+{
+    local iface
+    local VLANID
+    local IF_VLAN_RAW_DEVICE
+    iface=$1
+
+    case "$iface" in
+        vlan0*)
+                vconfig set_name_type VLAN_PLUS_VID
+                VLANID=`echo $iface|sed "s/vlan0*//"`
+                ;;
+        vlan*)
+                vconfig set_name_type VLAN_PLUS_VID_NO_PAD
+                VLANID=`echo $iface|sed "s/vlan0*//"`
+                ;;
+        eth*.0*)
+                vconfig set_name_type DEV_PLUS_VID
+                VLANID=`echo $iface|sed "s/eth[0-9][0-9]*<br/>.0*//g"`
+                IF_VLAN_RAW_DEVICE=`echo $iface|sed "s/<br/>(eth[0-9][0-9]*<br/>)<br/>..*/<br/>1/"`
+                ;;
+        eth*.*)
+                vconfig set_name_type DEV_PLUS_VID_NO_PAD
+                VLANID=`echo $iface|sed "s/eth[0-9][0-9]*<br/>.0*//g"`
+                IF_VLAN_RAW_DEVICE=`echo $iface|sed "s/<br/>(eth[0-9][0-9]*<br/>)<br/>..*/<br/>1/"`
+                ;;
+        *)      return 0
+                ;;
+    esac
+
+    if [ -n "$IF_VLAN_RAW_DEVICE" ]
+    then
+        if [ ! -x /usr/sbin/vconfig ]; then
+          return 0
+        fi
+
+        if ! ip link show dev "$IF_VLAN_RAW_DEVICE" > /dev/null; then
+            echo "$IF_VLAN_RAW_DEVICE does not exist, unable to create $iface"
+            return 1
+        fi
+        ip link set up dev $IF_VLAN_RAW_DEVICE
+        vconfig add $IF_VLAN_RAW_DEVICE $VLANID
+    fi
+
+    return 0
+}
+
+vlan_down()
+{
+    local iface
+    local IF_VLAN_RAW_DEVICE
+
+    iface=$1
+
+    case "$iface" in
+        eth*.0*)
+                IF_VLAN_RAW_DEVICE=`echo $IFACE|sed "s/<br/>(eth[0-9][0-9]*<br/>)<br/>..*/<br/>1/"`
+                ;;
+        eth*.*)
+                IF_VLAN_RAW_DEVICE=`echo $IFACE|sed "s/<br/>(eth[0-9][0-9]*<br/>)<br/>..*/<br/>1/"`
+                ;;
+        *)      return 0
+                ;;
+    esac
+
+    if [ -z "$IF_VLAN_RAW_DEVICE" ]; then
+        return 0
+    fi
+
+    if [ ! -x /usr/sbin/vconfig ]; then
+	return 0
+    fi
+
+    vconfig rem $face
+}
+
 ifup()
 {
 	if [ "$1" = "" ]; then
@@ -28,6 +105,9 @@
 		/bin/rm -f /var/run/dhcpcd-${1}.cache >/dev/null 2>&1
 		/sbin/dhcpcd $DHCPCD_ARGS ${1}
 	else
+		iface=`echo $ifcfg | awk '{print $1}'`
+		vlan_up $iface || return 1
+
 		/sbin/ifconfig $ifcfg
 	fi
 	return $?
@@ -63,12 +143,17 @@
 		fi
 	fi
 	# Always bring the interface itself down
1. /sbin/ifconfig ${1} down >/dev/null 2>&1
1. return $?
+	/sbin/ifconfig ${1} down >/dev/null 2>&1 || return 1
+
+	iface=`echo $ifcfg | awk '{print $1}'`
+	vlan_down $iface || return 1
+
+	return 1
 }

 iflist()
 {
+	local iface
 	for ifline in ${INTERFACES[@]}; do
 		if [ "$ifline" = "${ifline#!}" ]; then
 			printf " $ifline:<br/>t"
@@ -76,7 +161,20 @@
 			printf "$ifline:<br/>t"
 		fi
 		eval real_ifline=<br/>$${ifline#!}
1. 	echo $real_ifline
+
+		iface=`echo $real_ifline | awk '{print $1}'`
+		if [ x"$iface" != x"dhcp" ]; then
+		    is_run=`ifconfig $iface | grep 'inet addr'`
+		    if [ -z "$is_run" ]; then
+			run='( )'
+		    else
+			run='(*)'
+		    fi
+		else
+		    run='(?)'
+		fi
+
+		echo $real_ifline $run
 	done
 }

Можно скачать файлом: network_vlan.diff

В заключении немного об именовании интерфейсов и переменных в /etc/rc.conf.

Имя интерфейса с VLAN должно быть одним из:

  • eth#.# - если сразу после точки идет цифра отличная от нуля (ex.: eth2.1, eth2.815)
  • eth#.#### - если сразу после точки идет ноль (0), тогда обязательно VLAN ID должен быть расширен до 4 цифр (ex.: eth2.0001, eth2.0814). Что будет если вы этого не сделаете? Попробуйте сами, а найдете решение как обойти - дайте знать ;)

Есть ещё варианта интерсейсов vlan# и vlan####, но я с ними не сталкивался и ничего сказать не могу. Кто может дополнить - с радостью выслушаю :)

Внутри конфига /etc/rc.conf я выработал следующие правила для именования переменных с параметрами интерфейсов:

  • eth# - для основного интерфейса
  • eth#a# - для алиасов (eth2:1)
  • eth#v# или eth#v#### - для VLAN (eth2.1)

Примеры: eth2=“eth2 192.168.0.1 netmask 255.255.255.0” eth2a1=“eth2:1 192.168.1.130 netmask 255.255.255.255” eth2v814=“eth2.814 192.168.2.130 netmask 255.255.255.255”

Вот в общем и всё. Сейчас ещё патч запщу на http://bugs.archlinux.org.

UPD:

Хех… я не один такой оказывается: FS#10420 - VLAN support for Arch default network script

там же есть вариант патча для ethX.Y Сейчас своё добавлю ;)

UPD2:

Да, возможно придется добавить модуль ядра 8021q в строчку MODULES в /etc/rc.conf