dsaconfig: introduce package for UCI configuration of VLAN filter rules
[openwrt/staging/rmilecki.git] / package / network / config / dsaconfig / files / dsaconfig.sh
1 #!/bin/sh
2
3 . /lib/functions.sh
4 . /lib/functions/network.sh
5
6 switch_names=""
7
8 warn() {
9 echo "$@" >&2
10 }
11
12 clear_port_vlans() {
13 local port=$1
14 local self=$2
15 local vlans=$(bridge vlan show dev "$port" | sed -ne 's#^[^ ]* \+\([0-9]\+\).*$#\1#p')
16
17 local vlan
18 for vlan in $vlans; do
19 bridge vlan del vid "$vlan" dev "$port" $self
20 done
21 }
22
23 lookup_switch() {
24 local cfg=$1
25 local swname
26
27 config_get swname "$cfg" switch
28
29 # Auto-determine switch if not specified ...
30 if [ -z "$swname" ]; then
31 case "$switch_names" in
32 *\ *)
33 warn "VLAN section '$cfg' does not specify a switch but multiple switches present, using first one"
34 swname=${switch_names%% *}
35 ;;
36 *)
37 swname=${switch_names}
38 ;;
39 esac
40
41 # ... otherwise check if the referenced switch is declared
42 else
43 case " $switch_names " in
44 *" $swname "*) : ;;
45 *)
46 warn "Switch '$swname' specified by VLAN section '$cfg' does not exist"
47 return 1
48 ;;
49 esac
50 fi
51
52 export -n "switch=$swname"
53 }
54
55 validate_vid() {
56 local vid=$1
57
58 case "$vid" in
59 [1-9]|[1-9][0-9]|[1-9][0-9][0-9]|[1-4][0-9][0-9][0-9])
60 if [ $vid -gt 4096 ]; then
61 return 1
62 fi
63 ;;
64 *)
65 return 1
66 ;;
67 esac
68
69 return 0
70 }
71
72 setup_switch() {
73 local cfg=$1
74 local cpu
75
76 # Read configured CPU port from uci ...
77 config_get cpu "$cfg" cpu_port
78
79 # ... if unspecified, find first CPU port
80 if [ -z "$cpu" ]; then
81 local e
82 for e in /sys/class/net/*/dsa; do
83 if [ -d "$e" ]; then
84 cpu=${e%/dsa}
85 cpu=${cpu##*/}
86 break
87 fi
88 done
89 fi
90
91 # Bail out if we cannot determine the CPU port
92 if [ -z "$cpu" ]; then
93 warn "Unable to determine CPU port for switch '$cfg'"
94 return 1
95 fi
96
97 append switch_names "$cfg"
98
99 # Prevent netifd from picking up our switch bridge just yet
100 network_defer_device "$cfg"
101
102 # Increase MTU of CPU port to 1508 to accomodate for VID + DSA tag
103 ip link set "$cpu" mtu 1508
104 ip link set "$cpu" up
105
106 # (Re)create switch bridge device in case it is not yet set up
107 local filtering=$(cat "/sys/class/net/$cfg/bridge/vlan_filtering" 2>/dev/null)
108 if [ ${filtering:-0} != 1 ]; then
109 ip link set "$cfg" down 2>/dev/null
110 ip link delete dev "$cfg" 2>/dev/null
111 ip link add name "$cfg" type bridge
112 echo 1 > "/sys/class/net/$cfg/bridge/vlan_filtering"
113 fi
114
115 ip link set "$cfg" up
116
117 # Unbridge DSA ports and flush any VLAN filters on them, they're added back later
118 local port
119 for port in /sys/class/net/*"/upper_${cfg}"; do
120 if [ -e "$port" ]; then
121 port=${port%/upper_*}
122 port=${port##*/}
123
124 ip link set "$port" nomaster
125
126 # Unbridging the port should already clear VLANs, but be safe
127 clear_port_vlans "$port"
128 fi
129 done
130
131 # Clear any VLANs on the switch bridge, they're added back later
132 clear_port_vlans "$cfg" self
133 }
134
135 setup_switch_vlan() {
136 local cfg=$1
137 local switch vlan ports
138
139 config_get switch "$cfg" switch
140 config_get vlan "$cfg" vlan
141 config_get ports "$cfg" ports
142
143 lookup_switch "$cfg" || return 1
144 validate_vid "$vlan" || {
145 warn "VLAN section '$cfg' specifies an invalid VLAN ID '$vlan'"
146 return 1
147 }
148
149 # Setup ports
150 local port tag pvid
151 for port in $ports; do
152 tag=${port#*.}
153 port=${port%.*}
154 pvid=
155
156 if [ "$tag" != "$port" ] && [ "$tag" = t ]; then
157 tag=tagged
158 else
159 tag=untagged
160 fi
161
162 # Add the port to the switch bridge and delete the default
163 # VLAN 1 if it is not yet joined to the switch.
164 if [ ! -e "/sys/class/net/$port/upper_$switch" ]; then
165 ip link set dev "$port" up
166 ip link set dev "$port" master "$switch"
167
168 # Get rid of default VLAN 1
169 bridge vlan del vid 1 dev "$port"
170 fi
171
172 # Promote the first untagged VLAN of this port to the PVID
173 if [ "$tag" = untagged ] && ! bridge vlan show dev "$port" | grep -qi pvid; then
174 pvid=pvid
175 fi
176
177 # Add VLAN filter entry for port
178 bridge vlan add dev "$port" vid $vlan $pvid $tag
179 done
180
181 # Make the switch bridge itself handle the VLAN as well
182 bridge vlan add dev "$switch" self vid $vlan tagged
183 }
184
185 setup_switch_port() {
186 local cfg=$1
187 local switch port pvid tag
188
189 config_get port "$cfg" port
190 config_get pvid "$cfg" pvid
191
192 lookup_switch "$cfg" || return 1
193 validate_vid "$pvid" || {
194 warn "Port section '$cfg' specifies an invalid PVID '$pvid'"
195 return 1
196 }
197
198 # Disallow setting the PVID of the switch bridge itself
199 [ "$port" != "$switch" ] || {
200 warn "Port section '$cfg' must not change PVID of the switch bridge"
201 return 1
202 }
203
204 # Determine existing VLAN config
205 local vlanspec=$(bridge vlan show dev "$port" vid "$pvid" 2>/dev/null | sed -ne2p)
206 echo "$vlanspec" | grep -qi untagged && tag=untagged || tag=tagged
207
208 bridge vlan add vid "$pvid" dev "$port" pvid $tag
209 }
210
211 apply_config() {
212 config_load network
213 config_foreach setup_switch dsa
214
215 # If no switch is explicitely declared, synthesize switch0
216 if [ -z "$switch_names" ] && ! setup_switch switch0; then
217 warn "No DSA switches found"
218 return 1
219 fi
220
221 config_foreach setup_switch_vlan dsa_vlan
222 config_foreach setup_switch_port dsa_port
223
224 # Ready switch bridge devices
225 local switch
226 for switch in $switch_names; do
227 network_ready_device "$switch"
228 done
229 }
230
231 show_switch() {
232 local switch=$1
233
234 printf "Switch: %s\n" "$switch"
235 printf "VLAN/"
236
237 local port ports
238 for port in "/sys/class/net/$switch/lower_"*; do
239 port=${port##*/lower_}
240
241
242 printf " | %-5s" "$port"
243 append ports "$port"
244 done
245
246 printf " |\nLink:"
247
248 for port in $ports; do
249 local carrier=$(cat "/sys/class/net/$port/carrier")
250 local duplex=$(cat "/sys/class/net/$port/duplex")
251 local speed=$(cat "/sys/class/net/$port/speed")
252
253 if [ ${carrier:-0} -eq 0 ]; then
254 printf " | %-5s" "down"
255 else
256 [ "$duplex" = "full" ] && duplex=F || duplex=H
257 printf " | %4d%s" "$speed" "$duplex"
258 fi
259 done
260
261 local vlans=$(bridge vlan show dev "$switch" | sed -ne 's#^[^ ]* \+\([0-9]\+\).*$#\1#p')
262 local vlan
263 for vlan in $vlans; do
264 printf " |\n%4d " "$vlan"
265
266 for port in $ports; do
267 local pvid="" utag="" word
268 for word in $(bridge vlan show dev "$port" vid "$vlan"); do
269 case "$word" in
270 PVID) pvid="*" ;;
271 "$vlan") utag="t" ;;
272 Untagged) utag="u" ;;
273 esac
274 done
275
276 printf " | %-2s " "$utag$pvid"
277 done
278 done
279
280 printf " |\n\n"
281 }
282
283 add_switch() {
284 append switch_names "$1"
285 }
286
287 show_config() {
288 config_load network
289 config_foreach add_switch dsa
290
291 local switch
292 for switch in ${switch_names:-switch0}; do
293 show_switch "$switch"
294 done
295 }
296
297
298 case "$1" in
299 show) show_config ;;
300 apply) apply_config ;;
301 *)
302 echo "Usage: ${0##*/} show"
303 echo " ${0##*/} apply"
304 exit 1
305 ;;
306 esac