blob: 7c1803dd2996d76e0fd0892e0a83ba0edeb62f27 [file] [log] [blame]
Brandon Kimdab96f12021-02-18 11:21:37 -08001# Copyright 2021 Google LLC
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
William A. Kennington III7d6fa422021-02-08 17:04:02 -080015# Internal handler used for signalling child processes that they should
16# terminate.
17HandleTerm() {
18 GOT_TERM=1
19 if ShouldTerm && (( ${#CHILD_PIDS[@]} > 0 )); then
William A. Kennington IIId7989582022-05-27 16:41:44 -070020 kill -s TERM "${!CHILD_PIDS[@]}"
William A. Kennington III7d6fa422021-02-08 17:04:02 -080021 fi
22}
23
24# Sets up the signal handler and global variables needed to run interruptible
25# services that can be killed gracefully.
26InitTerm() {
27 declare -g -A CHILD_PIDS=()
28 declare -g GOT_TERM=0
29 declare -g SUPPRESS_TERM=0
30 trap HandleTerm TERM
31}
32
33# Used to suppress the handling of SIGTERM for critical components that should
34# not respect SIGTERM. To finish suppressing, use UnsuppressTerm()
35SuppressTerm() {
36 SUPPRESS_TERM=$((SUPPRESS_TERM + 1))
37}
38
39# Stops suppressing SIGTERM for a single invocation of SuppresssTerm()
40UnsuppressTerm() {
41 SUPPRESS_TERM=$((SUPPRESS_TERM - 1))
42}
43
44# Determines if we got a SIGTERM and should respect it
45ShouldTerm() {
46 (( GOT_TERM == 1 && SUPPRESS_TERM == 0 ))
47}
48
49# Internal, ensures that functions called in a subprocess properly initialize
50# their SIGTERM handling logic
51RunInterruptibleFunction() {
52 CHILD_PIDS=()
53 trap HandleTerm TERM
54 "$@"
55}
56
57# Runs the provided commandline in the background, and passes any received
58# SIGTERMS to the child. Can be waited on using WaitInterruptibleBg
59RunInterruptibleBg() {
60 if ShouldTerm; then
61 return 143
62 fi
63 if [ "$(type -t "$1")" = "function" ]; then
64 RunInterruptibleFunction "$@" &
65 else
66 "$@" &
67 fi
68 CHILD_PIDS["$!"]=1
69}
70
71# Runs the provided commandline to completion, and passes any received
72# SIGTERMS to the child.
73RunInterruptible() {
74 RunInterruptibleBg "$@" || return
75 local child_pid="$!"
76 wait "$child_pid" || true
77 unset CHILD_PIDS["$child_pid"]
78 wait "$child_pid"
79}
80
81# Waits until all of the RunInterruptibleBg() jobs have terminated
82WaitInterruptibleBg() {
83 local wait_on=("${!CHILD_PIDS[@]}")
84 if (( ${#wait_on[@]} > 0 )); then
85 wait "${wait_on[@]}" || true
86 CHILD_PIDS=()
87 local rc=0
88 local id
89 for id in "${wait_on[@]}"; do
90 wait "$id" || rc=$?
91 done
92 return $rc
93 fi
94}
95
William A. Kennington IIId7989582022-05-27 16:41:44 -070096# Runs the provided commandline to completion, capturing stdout
97# into a variable
98CaptureInterruptible() {
99 local var="$1"
100 shift
101 if ShouldTerm; then
102 return 143
103 fi
104 coproc "$@" || return
105 local child_pid="$COPROC_PID"
106 CHILD_PIDS["$child_pid"]=1
107 exec {COPROC[1]}>&-
108 read -d $'\0' -ru "${COPROC[0]}" "$var" || true
109 wait "$child_pid" || true
110 unset CHILD_PIDS[$child_pid]
111 wait "$child_pid"
112}
113
William A. Kennington III7d6fa422021-02-08 17:04:02 -0800114# Determines if an address could be a valid IPv4 address
115# NOTE: this doesn't sanitize invalid IPv4 addresses
116IsIPv4() {
117 local ip="$1"
118
119 [[ "$ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]
120}
121
122# Takes lines of text from an application on stdin and parses out a single
123# MAC address per line of input.
124ParseMACFromLine() {
125 sed -n 's,.*\(\([0-9a-fA-F]\{2\}:\)\{5\}[0-9a-fA-F]\{2\}\).*,\1,p'
126}
127
128# Looks up the MAC address of the IPv4 neighbor using ARP
129DetermineNeighbor4() {
130 local netdev="$1"
131 local ip="$2"
132
133 # Grep intentionally prevented from returning an error to preserve the error
134 # value of arping
135 RunInterruptible arping -f -c 5 -w 5 -I "$netdev" "$ip" | \
136 { grep 'reply from' || true; } | ParseMACFromLine
137}
138
139# Looks up the MAC address of the IPv6 neighbor using ICMPv6 ND
140DetermineNeighbor6() {
141 local netdev="$1"
142 local ip="$2"
143
144 RunInterruptible ndisc6 -1 -r 5 -w 1000 -q "$ip" "$netdev"
145}
146
147# Looks up the MAC address of the neighbor regardless of type
148DetermineNeighbor() {
149 local netdev="$1"
150 local ip="$2"
151
152 if IsIPv4 "$ip"; then
153 DetermineNeighbor4 "$netdev" "$ip"
154 else
155 DetermineNeighbor6 "$netdev" "$ip"
156 fi
157}
158
159# Performs a mapper call to get the subroot for the object root
160# with a maxdepth and list of required interfaces. Returns a streamed list
161# of JSON objects that contain an { object, service }.
162GetSubTree() {
163 local root="$1"
164 shift
165 local max_depth="$1"
166 shift
167
168 busctl --json=short call \
169 'xyz.openbmc_project.ObjectMapper' \
170 '/xyz/openbmc_project/object_mapper' \
171 'xyz.openbmc_project.ObjectMapper' \
172 'GetSubTree' sias "$root" "$max_depth" "$#" "$@" | \
173 jq -c '.data[0] | to_entries[] | { object: .key, service: (.value | keys[0]) }'
174}
175
176# Returns all of the properties for a DBus interface on an object as a JSON
177# object where the keys are the property names
178GetProperties() {
179 local service="$1"
180 local object="$2"
181 local interface="$3"
182
183 busctl --json=short call \
184 "$service" \
185 "$object" \
186 'org.freedesktop.DBus.Properties' \
187 'GetAll' s "$interface" | \
188 jq -c '.data[0] | with_entries({ key, value: .value.data })'
189}
190
191# Returns the property for a DBus interface on an object
192GetProperty() {
193 local service="$1"
194 local object="$2"
195 local interface="$3"
196 local property="$4"
197
198 busctl --json=short call \
199 "$service" \
200 "$object" \
201 'org.freedesktop.DBus.Properties' \
202 'Get' ss "$interface" "$property" | \
203 jq -r '.data[0].data'
204}
205
206# Deletes any OpenBMC DBus object from a service
207DeleteObject() {
208 local service="$1"
209 local object="$2"
210
211 busctl call \
212 "$service" \
213 "$object" \
214 'xyz.openbmc_project.Object.Delete' \
215 'Delete'
216}
217
218# Transforms the given JSON dictionary into bash local variable
219# statements that can be directly evaluated by the interpreter
220JSONToVars() {
221 jq -r 'to_entries[] | @sh "local \(.key)=\(.value)"'
222}
223
224# Returns the DBus object root for the ethernet interface
225EthObjRoot() {
226 local netdev="$1"
227
228 echo "/xyz/openbmc_project/network/$netdev"
229}
230
231# Returns the DBus object root for the static neighbors of an intrerface
232StaticNeighborObjRoot() {
233 local netdev="$1"
234
235 echo "$(EthObjRoot "$netdev")/static_neighbor"
236}
237
238# Returns all of the neighbor { service, object } data for an interface as if
239# a call to GetSubTree() was made
240GetNeighborObjects() {
241 local netdev="$1"
242
243 GetSubTree "$(StaticNeighborObjRoot "$netdev")" 0 \
244 'xyz.openbmc_project.Network.Neighbor'
245}
246
247# Returns the neighbor properties as a JSON object
248GetNeighbor() {
249 local service="$1"
250 local object="$2"
251
252 GetProperties "$service" "$object" 'xyz.openbmc_project.Network.Neighbor'
253}
254
255# Adds a static neighbor to the system network daemon
256AddNeighbor() {
257 local service="$1"
258 local netdev="$2"
259 local ip="$3"
260 local mac="$4"
261
262 busctl call \
263 "$service" \
264 "$(EthObjRoot "$netdev")" \
265 'xyz.openbmc_project.Network.Neighbor.CreateStatic' \
266 'Neighbor' ss "$ip" "$mac" >/dev/null
267}
268
269# Returns all of the IP { service, object } data for an interface as if
270# a call to GetSubTree() was made
271GetIPObjects() {
272 local netdev="$1"
273
274 GetSubTree "$(EthObjRoot "$netdev")" 0 \
275 'xyz.openbmc_project.Network.IP'
276}
277
278# Returns the IP properties as a JSON object
279GetIP() {
280 local service="$1"
281 local object="$2"
282
283 GetProperties "$service" "$object" 'xyz.openbmc_project.Network.IP'
284}
285
William A. Kennington IIIb163a2c2021-05-20 17:33:01 -0700286# Returns the Gateway address for the interface and type
287GetGateways() {
288 local service="$1"
289 local netdev="$2"
290
291 # We fetch both the system properties and the netdev specific properties
292 # as OpenBMC is in the process of transitioning these to the netdev object
293 # but the migration is not yet complete.
294 {
295 GetProperties "$service" '/xyz/openbmc_project/network/config' \
296 'xyz.openbmc_project.Network.SystemConfiguration'
297 GetProperties "$service" "$(EthObjRoot "$netdev")" \
298 'xyz.openbmc_project.Network.EthernetInterface'
299 } | jq -s '
300 . | map(
301 if .DefaultGateway != "" then
302 {DefaultGateway: .DefaultGateway}
303 else
304 {}
305 end +
306 if .DefaultGateway6 != "" then
307 {DefaultGateway6: .DefaultGateway6}
308 else
309 {}
310 end
311 ) | {DefaultGateway: "", DefaultGateway6: ""} + add'
312}
313
William A. Kennington III7d6fa422021-02-08 17:04:02 -0800314# Adds a static IP to the system network daemon
315AddIP() {
316 local service="$1"
317 local netdev="$2"
318 local ip="$3"
319 local prefix="$4"
320
321 local protocol='xyz.openbmc_project.Network.IP.Protocol.IPv4'
322 if ! IsIPv4 "$ip"; then
323 protocol='xyz.openbmc_project.Network.IP.Protocol.IPv6'
324 fi
325
326 busctl call \
327 "$service" \
328 "$(EthObjRoot "$netdev")" \
329 'xyz.openbmc_project.Network.IP.Create' \
330 'IP' ssys "$protocol" "$ip" "$prefix" '' >/dev/null
331}
332
333# Determines if two IP addresses have the same address family
334# IE: Both are IPv4 or both are IPv6
335MatchingAF() {
336 local rc1=0 rc2=0
337 IsIPv4 "$1" || rc1=$?
338 IsIPv4 "$2" || rc2=$?
339 (( rc1 == rc2 ))
340}
341
342# Checks to see if the machine has the provided IP address information
343# already configured. If not, it deletes all of the information for that
344# address family and adds the provided IP address.
345UpdateIP() {
346 local service="$1"
347 local netdev="$2"
348 local ip="$3"
349 local prefix="$4"
350
351 local should_add=1
352 local delete_services=()
353 local delete_objects=()
354 local entry
355 while read entry; do
356 eval "$(echo "$entry" | JSONToVars)" || return $?
357 eval "$(GetIP "$service" "$object" | JSONToVars)" || return $?
358 if [ "$(normalize_ip "$Address")" = "$(normalize_ip "$ip")" ] && \
359 [ "$PrefixLength" = "$prefix" ]; then
360 should_add=0
361 elif MatchingAF "$ip" "$Address"; then
362 echo "Deleting spurious IP: $Address/$PrefixLength" >&2
363 delete_services+=("$service")
364 delete_objects+=("$object")
365 fi
366 done < <(GetIPObjects "$netdev")
367
368 local i
369 for (( i=0; i<${#delete_objects[@]}; ++i )); do
William A. Kennington III2be05362021-11-05 02:58:59 -0700370 DeleteObject "${delete_services[$i]}" "${delete_objects[$i]}" || true
William A. Kennington III7d6fa422021-02-08 17:04:02 -0800371 done
372
373 if (( should_add == 0 )); then
374 echo "Not adding IP: $ip/$prefix" >&2
375 else
376 echo "Adding IP: $ip/$prefix" >&2
377 AddIP "$service" "$netdev" "$ip" "$prefix" || return $?
378 fi
379}
380
381# Sets the system gateway property to the provided IP address if not already
382# set to the current value.
383UpdateGateway() {
384 local service="$1"
William A. Kennington III2d6858d2022-02-11 03:08:25 -0800385 local netdev="$2"
386 local ip="$3"
William A. Kennington III7d6fa422021-02-08 17:04:02 -0800387
William A. Kennington III2d6858d2022-02-11 03:08:25 -0800388 local object="$(EthObjRoot "$netdev")"
389 local interface='xyz.openbmc_project.Network.EthernetInterface'
William A. Kennington III7d6fa422021-02-08 17:04:02 -0800390 local property='DefaultGateway'
391 if ! IsIPv4 "$ip"; then
392 property='DefaultGateway6'
393 fi
394
395 local current_ip
396 current_ip="$(GetProperty "$service" "$object" "$interface" "$property")" || \
397 return $?
398 if [ -n "$current_ip" ] && \
399 [ "$(normalize_ip "$ip")" = "$(normalize_ip "$current_ip")" ]; then
400 echo "Not reconfiguring gateway: $ip" >&2
401 return 0
402 fi
403
404 echo "Setting gateway: $ip" >&2
405 busctl set-property "$service" "$object" "$interface" "$property" s "$ip"
406}
407
408# Checks to see if the machine has the provided neighbor information
409# already configured. If not, it deletes all of the information for that
410# address family and adds the provided neighbor entry.
411UpdateNeighbor() {
412 local service="$1"
413 local netdev="$2"
414 local ip="$3"
415 local mac="$4"
416
417 local should_add=1
418 local delete_services=()
419 local delete_objects=()
420 local entry
421 while read entry; do
422 eval "$(echo "$entry" | JSONToVars)" || return $?
423 eval "$(GetNeighbor "$service" "$object" | JSONToVars)" || return $?
424 if [ "$(normalize_ip "$IPAddress")" = "$(normalize_ip "$ip")" ] && \
425 [ "$(normalize_mac "$MACAddress")" = "$(normalize_mac "$mac")" ]; then
426 should_add=0
427 elif MatchingAF "$ip" "$IPAddress"; then
428 echo "Deleting spurious neighbor: $IPAddress $MACAddress" >&2
429 delete_services+=("$service")
430 delete_objects+=("$object")
431 fi
432 done < <(GetNeighborObjects "$netdev" 2>/dev/null)
433
434 local i
435 for (( i=0; i<${#delete_objects[@]}; ++i )); do
William A. Kennington III2be05362021-11-05 02:58:59 -0700436 DeleteObject "${delete_services[$i]}" "${delete_objects[$i]}" || true
William A. Kennington III7d6fa422021-02-08 17:04:02 -0800437 done
438
439 if (( should_add == 0 )); then
440 echo "Not adding neighbor: $ip $mac" >&2
441 else
442 echo "Adding neighbor: $ip $mac" >&2
443 AddNeighbor "$service" "$netdev" "$ip" "$mac" || return $?
444 fi
445}
William A. Kennington IIIb163a2c2021-05-20 17:33:01 -0700446
447# Determines the ip and mac of the IPv6 router
448DiscoverRouter6() {
449 local netdev="$1"
450 local retries="$2"
451 local timeout="$3"
452 local router="${4-}"
453
454 local output
455 local st=0
456 local args=(-1 -w "$timeout" -n $router "$netdev")
457 if (( retries < 0 )); then
458 args+=(-d)
459 else
460 args+=(-r "$retries")
461 fi
William A. Kennington IIId7989582022-05-27 16:41:44 -0700462 CaptureInterruptible output rdisc6 "${args[@]}" || st=$?
William A. Kennington IIIb163a2c2021-05-20 17:33:01 -0700463 if (( st != 0 )); then
464 echo "rdisc6 failed with: " >&2
465 echo "$output" >&2
466 return $st
467 fi
468
469 local ip="$(echo "$output" | grep 'from' | awk '{print $2}')"
470 local mac="$(echo "$output" | grep 'Source link-layer' | ParseMACFromLine)"
William A. Kennington III379b0612021-11-04 02:42:30 -0700471 local staddr="$(echo "$output" | grep 'Stateful address conf.*Yes')"
472 printf '{"router_ip":"%s","router_mac":"%s","stateful_address":"%s"}\n' \
473 "$ip" "$mac" "$staddr"
William A. Kennington IIIb163a2c2021-05-20 17:33:01 -0700474}
William A. Kennington IIIa7af2e02022-02-13 22:47:39 -0800475
476# Sets the network configuration of an interface to be static
477SetStatic() {
478 local service="$1"
479 local netdev="$2"
480
481 echo "Disabling DHCP" >&2
482 busctl set-property "$service" "$(EthObjRoot "$netdev")" \
483 xyz.openbmc_project.Network.EthernetInterface DHCPEnabled \
484 s xyz.openbmc_project.Network.EthernetInterface.DHCPConf.none
485}