blob: d96024b9890401f38f74d81b5b258c807e8fa932 [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
20 kill "${!CHILD_PIDS[@]}"
21 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
96# Determines if an address could be a valid IPv4 address
97# NOTE: this doesn't sanitize invalid IPv4 addresses
98IsIPv4() {
99 local ip="$1"
100
101 [[ "$ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]
102}
103
104# Takes lines of text from an application on stdin and parses out a single
105# MAC address per line of input.
106ParseMACFromLine() {
107 sed -n 's,.*\(\([0-9a-fA-F]\{2\}:\)\{5\}[0-9a-fA-F]\{2\}\).*,\1,p'
108}
109
110# Looks up the MAC address of the IPv4 neighbor using ARP
111DetermineNeighbor4() {
112 local netdev="$1"
113 local ip="$2"
114
115 # Grep intentionally prevented from returning an error to preserve the error
116 # value of arping
117 RunInterruptible arping -f -c 5 -w 5 -I "$netdev" "$ip" | \
118 { grep 'reply from' || true; } | ParseMACFromLine
119}
120
121# Looks up the MAC address of the IPv6 neighbor using ICMPv6 ND
122DetermineNeighbor6() {
123 local netdev="$1"
124 local ip="$2"
125
126 RunInterruptible ndisc6 -1 -r 5 -w 1000 -q "$ip" "$netdev"
127}
128
129# Looks up the MAC address of the neighbor regardless of type
130DetermineNeighbor() {
131 local netdev="$1"
132 local ip="$2"
133
134 if IsIPv4 "$ip"; then
135 DetermineNeighbor4 "$netdev" "$ip"
136 else
137 DetermineNeighbor6 "$netdev" "$ip"
138 fi
139}
140
141# Performs a mapper call to get the subroot for the object root
142# with a maxdepth and list of required interfaces. Returns a streamed list
143# of JSON objects that contain an { object, service }.
144GetSubTree() {
145 local root="$1"
146 shift
147 local max_depth="$1"
148 shift
149
150 busctl --json=short call \
151 'xyz.openbmc_project.ObjectMapper' \
152 '/xyz/openbmc_project/object_mapper' \
153 'xyz.openbmc_project.ObjectMapper' \
154 'GetSubTree' sias "$root" "$max_depth" "$#" "$@" | \
155 jq -c '.data[0] | to_entries[] | { object: .key, service: (.value | keys[0]) }'
156}
157
158# Returns all of the properties for a DBus interface on an object as a JSON
159# object where the keys are the property names
160GetProperties() {
161 local service="$1"
162 local object="$2"
163 local interface="$3"
164
165 busctl --json=short call \
166 "$service" \
167 "$object" \
168 'org.freedesktop.DBus.Properties' \
169 'GetAll' s "$interface" | \
170 jq -c '.data[0] | with_entries({ key, value: .value.data })'
171}
172
173# Returns the property for a DBus interface on an object
174GetProperty() {
175 local service="$1"
176 local object="$2"
177 local interface="$3"
178 local property="$4"
179
180 busctl --json=short call \
181 "$service" \
182 "$object" \
183 'org.freedesktop.DBus.Properties' \
184 'Get' ss "$interface" "$property" | \
185 jq -r '.data[0].data'
186}
187
188# Deletes any OpenBMC DBus object from a service
189DeleteObject() {
190 local service="$1"
191 local object="$2"
192
193 busctl call \
194 "$service" \
195 "$object" \
196 'xyz.openbmc_project.Object.Delete' \
197 'Delete'
198}
199
200# Transforms the given JSON dictionary into bash local variable
201# statements that can be directly evaluated by the interpreter
202JSONToVars() {
203 jq -r 'to_entries[] | @sh "local \(.key)=\(.value)"'
204}
205
206# Returns the DBus object root for the ethernet interface
207EthObjRoot() {
208 local netdev="$1"
209
210 echo "/xyz/openbmc_project/network/$netdev"
211}
212
213# Returns the DBus object root for the static neighbors of an intrerface
214StaticNeighborObjRoot() {
215 local netdev="$1"
216
217 echo "$(EthObjRoot "$netdev")/static_neighbor"
218}
219
220# Returns all of the neighbor { service, object } data for an interface as if
221# a call to GetSubTree() was made
222GetNeighborObjects() {
223 local netdev="$1"
224
225 GetSubTree "$(StaticNeighborObjRoot "$netdev")" 0 \
226 'xyz.openbmc_project.Network.Neighbor'
227}
228
229# Returns the neighbor properties as a JSON object
230GetNeighbor() {
231 local service="$1"
232 local object="$2"
233
234 GetProperties "$service" "$object" 'xyz.openbmc_project.Network.Neighbor'
235}
236
237# Adds a static neighbor to the system network daemon
238AddNeighbor() {
239 local service="$1"
240 local netdev="$2"
241 local ip="$3"
242 local mac="$4"
243
244 busctl call \
245 "$service" \
246 "$(EthObjRoot "$netdev")" \
247 'xyz.openbmc_project.Network.Neighbor.CreateStatic' \
248 'Neighbor' ss "$ip" "$mac" >/dev/null
249}
250
251# Returns all of the IP { service, object } data for an interface as if
252# a call to GetSubTree() was made
253GetIPObjects() {
254 local netdev="$1"
255
256 GetSubTree "$(EthObjRoot "$netdev")" 0 \
257 'xyz.openbmc_project.Network.IP'
258}
259
260# Returns the IP properties as a JSON object
261GetIP() {
262 local service="$1"
263 local object="$2"
264
265 GetProperties "$service" "$object" 'xyz.openbmc_project.Network.IP'
266}
267
268# Adds a static IP to the system network daemon
269AddIP() {
270 local service="$1"
271 local netdev="$2"
272 local ip="$3"
273 local prefix="$4"
274
275 local protocol='xyz.openbmc_project.Network.IP.Protocol.IPv4'
276 if ! IsIPv4 "$ip"; then
277 protocol='xyz.openbmc_project.Network.IP.Protocol.IPv6'
278 fi
279
280 busctl call \
281 "$service" \
282 "$(EthObjRoot "$netdev")" \
283 'xyz.openbmc_project.Network.IP.Create' \
284 'IP' ssys "$protocol" "$ip" "$prefix" '' >/dev/null
285}
286
287# Determines if two IP addresses have the same address family
288# IE: Both are IPv4 or both are IPv6
289MatchingAF() {
290 local rc1=0 rc2=0
291 IsIPv4 "$1" || rc1=$?
292 IsIPv4 "$2" || rc2=$?
293 (( rc1 == rc2 ))
294}
295
296# Checks to see if the machine has the provided IP address information
297# already configured. If not, it deletes all of the information for that
298# address family and adds the provided IP address.
299UpdateIP() {
300 local service="$1"
301 local netdev="$2"
302 local ip="$3"
303 local prefix="$4"
304
305 local should_add=1
306 local delete_services=()
307 local delete_objects=()
308 local entry
309 while read entry; do
310 eval "$(echo "$entry" | JSONToVars)" || return $?
311 eval "$(GetIP "$service" "$object" | JSONToVars)" || return $?
312 if [ "$(normalize_ip "$Address")" = "$(normalize_ip "$ip")" ] && \
313 [ "$PrefixLength" = "$prefix" ]; then
314 should_add=0
315 elif MatchingAF "$ip" "$Address"; then
316 echo "Deleting spurious IP: $Address/$PrefixLength" >&2
317 delete_services+=("$service")
318 delete_objects+=("$object")
319 fi
320 done < <(GetIPObjects "$netdev")
321
322 local i
323 for (( i=0; i<${#delete_objects[@]}; ++i )); do
324 DeleteObject "${delete_services[$i]}" "${delete_objects[$i]}" || return $?
325 done
326
327 if (( should_add == 0 )); then
328 echo "Not adding IP: $ip/$prefix" >&2
329 else
330 echo "Adding IP: $ip/$prefix" >&2
331 AddIP "$service" "$netdev" "$ip" "$prefix" || return $?
332 fi
333}
334
335# Sets the system gateway property to the provided IP address if not already
336# set to the current value.
337UpdateGateway() {
338 local service="$1"
339 local ip="$2"
340
341 local object='/xyz/openbmc_project/network/config'
342 local interface='xyz.openbmc_project.Network.SystemConfiguration'
343 local property='DefaultGateway'
344 if ! IsIPv4 "$ip"; then
345 property='DefaultGateway6'
346 fi
347
348 local current_ip
349 current_ip="$(GetProperty "$service" "$object" "$interface" "$property")" || \
350 return $?
351 if [ -n "$current_ip" ] && \
352 [ "$(normalize_ip "$ip")" = "$(normalize_ip "$current_ip")" ]; then
353 echo "Not reconfiguring gateway: $ip" >&2
354 return 0
355 fi
356
357 echo "Setting gateway: $ip" >&2
358 busctl set-property "$service" "$object" "$interface" "$property" s "$ip"
359}
360
361# Checks to see if the machine has the provided neighbor information
362# already configured. If not, it deletes all of the information for that
363# address family and adds the provided neighbor entry.
364UpdateNeighbor() {
365 local service="$1"
366 local netdev="$2"
367 local ip="$3"
368 local mac="$4"
369
370 local should_add=1
371 local delete_services=()
372 local delete_objects=()
373 local entry
374 while read entry; do
375 eval "$(echo "$entry" | JSONToVars)" || return $?
376 eval "$(GetNeighbor "$service" "$object" | JSONToVars)" || return $?
377 if [ "$(normalize_ip "$IPAddress")" = "$(normalize_ip "$ip")" ] && \
378 [ "$(normalize_mac "$MACAddress")" = "$(normalize_mac "$mac")" ]; then
379 should_add=0
380 elif MatchingAF "$ip" "$IPAddress"; then
381 echo "Deleting spurious neighbor: $IPAddress $MACAddress" >&2
382 delete_services+=("$service")
383 delete_objects+=("$object")
384 fi
385 done < <(GetNeighborObjects "$netdev" 2>/dev/null)
386
387 local i
388 for (( i=0; i<${#delete_objects[@]}; ++i )); do
389 DeleteObject "${delete_services[$i]}" "${delete_objects[$i]}" || return $?
390 done
391
392 if (( should_add == 0 )); then
393 echo "Not adding neighbor: $ip $mac" >&2
394 else
395 echo "Adding neighbor: $ip $mac" >&2
396 AddNeighbor "$service" "$netdev" "$ip" "$mac" || return $?
397 fi
398}