blob: 080afed80ed3f0ebe82619fcd5474b59334cd0c1 [file] [log] [blame]
William A. Kennington III7d6fa422021-02-08 17:04:02 -08001# Internal handler used for signalling child processes that they should
2# terminate.
3HandleTerm() {
4 GOT_TERM=1
5 if ShouldTerm && (( ${#CHILD_PIDS[@]} > 0 )); then
6 kill "${!CHILD_PIDS[@]}"
7 fi
8}
9
10# Sets up the signal handler and global variables needed to run interruptible
11# services that can be killed gracefully.
12InitTerm() {
13 declare -g -A CHILD_PIDS=()
14 declare -g GOT_TERM=0
15 declare -g SUPPRESS_TERM=0
16 trap HandleTerm TERM
17}
18
19# Used to suppress the handling of SIGTERM for critical components that should
20# not respect SIGTERM. To finish suppressing, use UnsuppressTerm()
21SuppressTerm() {
22 SUPPRESS_TERM=$((SUPPRESS_TERM + 1))
23}
24
25# Stops suppressing SIGTERM for a single invocation of SuppresssTerm()
26UnsuppressTerm() {
27 SUPPRESS_TERM=$((SUPPRESS_TERM - 1))
28}
29
30# Determines if we got a SIGTERM and should respect it
31ShouldTerm() {
32 (( GOT_TERM == 1 && SUPPRESS_TERM == 0 ))
33}
34
35# Internal, ensures that functions called in a subprocess properly initialize
36# their SIGTERM handling logic
37RunInterruptibleFunction() {
38 CHILD_PIDS=()
39 trap HandleTerm TERM
40 "$@"
41}
42
43# Runs the provided commandline in the background, and passes any received
44# SIGTERMS to the child. Can be waited on using WaitInterruptibleBg
45RunInterruptibleBg() {
46 if ShouldTerm; then
47 return 143
48 fi
49 if [ "$(type -t "$1")" = "function" ]; then
50 RunInterruptibleFunction "$@" &
51 else
52 "$@" &
53 fi
54 CHILD_PIDS["$!"]=1
55}
56
57# Runs the provided commandline to completion, and passes any received
58# SIGTERMS to the child.
59RunInterruptible() {
60 RunInterruptibleBg "$@" || return
61 local child_pid="$!"
62 wait "$child_pid" || true
63 unset CHILD_PIDS["$child_pid"]
64 wait "$child_pid"
65}
66
67# Waits until all of the RunInterruptibleBg() jobs have terminated
68WaitInterruptibleBg() {
69 local wait_on=("${!CHILD_PIDS[@]}")
70 if (( ${#wait_on[@]} > 0 )); then
71 wait "${wait_on[@]}" || true
72 CHILD_PIDS=()
73 local rc=0
74 local id
75 for id in "${wait_on[@]}"; do
76 wait "$id" || rc=$?
77 done
78 return $rc
79 fi
80}
81
82# Determines if an address could be a valid IPv4 address
83# NOTE: this doesn't sanitize invalid IPv4 addresses
84IsIPv4() {
85 local ip="$1"
86
87 [[ "$ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]
88}
89
90# Takes lines of text from an application on stdin and parses out a single
91# MAC address per line of input.
92ParseMACFromLine() {
93 sed -n 's,.*\(\([0-9a-fA-F]\{2\}:\)\{5\}[0-9a-fA-F]\{2\}\).*,\1,p'
94}
95
96# Looks up the MAC address of the IPv4 neighbor using ARP
97DetermineNeighbor4() {
98 local netdev="$1"
99 local ip="$2"
100
101 # Grep intentionally prevented from returning an error to preserve the error
102 # value of arping
103 RunInterruptible arping -f -c 5 -w 5 -I "$netdev" "$ip" | \
104 { grep 'reply from' || true; } | ParseMACFromLine
105}
106
107# Looks up the MAC address of the IPv6 neighbor using ICMPv6 ND
108DetermineNeighbor6() {
109 local netdev="$1"
110 local ip="$2"
111
112 RunInterruptible ndisc6 -1 -r 5 -w 1000 -q "$ip" "$netdev"
113}
114
115# Looks up the MAC address of the neighbor regardless of type
116DetermineNeighbor() {
117 local netdev="$1"
118 local ip="$2"
119
120 if IsIPv4 "$ip"; then
121 DetermineNeighbor4 "$netdev" "$ip"
122 else
123 DetermineNeighbor6 "$netdev" "$ip"
124 fi
125}
126
127# Performs a mapper call to get the subroot for the object root
128# with a maxdepth and list of required interfaces. Returns a streamed list
129# of JSON objects that contain an { object, service }.
130GetSubTree() {
131 local root="$1"
132 shift
133 local max_depth="$1"
134 shift
135
136 busctl --json=short call \
137 'xyz.openbmc_project.ObjectMapper' \
138 '/xyz/openbmc_project/object_mapper' \
139 'xyz.openbmc_project.ObjectMapper' \
140 'GetSubTree' sias "$root" "$max_depth" "$#" "$@" | \
141 jq -c '.data[0] | to_entries[] | { object: .key, service: (.value | keys[0]) }'
142}
143
144# Returns all of the properties for a DBus interface on an object as a JSON
145# object where the keys are the property names
146GetProperties() {
147 local service="$1"
148 local object="$2"
149 local interface="$3"
150
151 busctl --json=short call \
152 "$service" \
153 "$object" \
154 'org.freedesktop.DBus.Properties' \
155 'GetAll' s "$interface" | \
156 jq -c '.data[0] | with_entries({ key, value: .value.data })'
157}
158
159# Returns the property for a DBus interface on an object
160GetProperty() {
161 local service="$1"
162 local object="$2"
163 local interface="$3"
164 local property="$4"
165
166 busctl --json=short call \
167 "$service" \
168 "$object" \
169 'org.freedesktop.DBus.Properties' \
170 'Get' ss "$interface" "$property" | \
171 jq -r '.data[0].data'
172}
173
174# Deletes any OpenBMC DBus object from a service
175DeleteObject() {
176 local service="$1"
177 local object="$2"
178
179 busctl call \
180 "$service" \
181 "$object" \
182 'xyz.openbmc_project.Object.Delete' \
183 'Delete'
184}
185
186# Transforms the given JSON dictionary into bash local variable
187# statements that can be directly evaluated by the interpreter
188JSONToVars() {
189 jq -r 'to_entries[] | @sh "local \(.key)=\(.value)"'
190}
191
192# Returns the DBus object root for the ethernet interface
193EthObjRoot() {
194 local netdev="$1"
195
196 echo "/xyz/openbmc_project/network/$netdev"
197}
198
199# Returns the DBus object root for the static neighbors of an intrerface
200StaticNeighborObjRoot() {
201 local netdev="$1"
202
203 echo "$(EthObjRoot "$netdev")/static_neighbor"
204}
205
206# Returns all of the neighbor { service, object } data for an interface as if
207# a call to GetSubTree() was made
208GetNeighborObjects() {
209 local netdev="$1"
210
211 GetSubTree "$(StaticNeighborObjRoot "$netdev")" 0 \
212 'xyz.openbmc_project.Network.Neighbor'
213}
214
215# Returns the neighbor properties as a JSON object
216GetNeighbor() {
217 local service="$1"
218 local object="$2"
219
220 GetProperties "$service" "$object" 'xyz.openbmc_project.Network.Neighbor'
221}
222
223# Adds a static neighbor to the system network daemon
224AddNeighbor() {
225 local service="$1"
226 local netdev="$2"
227 local ip="$3"
228 local mac="$4"
229
230 busctl call \
231 "$service" \
232 "$(EthObjRoot "$netdev")" \
233 'xyz.openbmc_project.Network.Neighbor.CreateStatic' \
234 'Neighbor' ss "$ip" "$mac" >/dev/null
235}
236
237# Returns all of the IP { service, object } data for an interface as if
238# a call to GetSubTree() was made
239GetIPObjects() {
240 local netdev="$1"
241
242 GetSubTree "$(EthObjRoot "$netdev")" 0 \
243 'xyz.openbmc_project.Network.IP'
244}
245
246# Returns the IP properties as a JSON object
247GetIP() {
248 local service="$1"
249 local object="$2"
250
251 GetProperties "$service" "$object" 'xyz.openbmc_project.Network.IP'
252}
253
254# Adds a static IP to the system network daemon
255AddIP() {
256 local service="$1"
257 local netdev="$2"
258 local ip="$3"
259 local prefix="$4"
260
261 local protocol='xyz.openbmc_project.Network.IP.Protocol.IPv4'
262 if ! IsIPv4 "$ip"; then
263 protocol='xyz.openbmc_project.Network.IP.Protocol.IPv6'
264 fi
265
266 busctl call \
267 "$service" \
268 "$(EthObjRoot "$netdev")" \
269 'xyz.openbmc_project.Network.IP.Create' \
270 'IP' ssys "$protocol" "$ip" "$prefix" '' >/dev/null
271}
272
273# Determines if two IP addresses have the same address family
274# IE: Both are IPv4 or both are IPv6
275MatchingAF() {
276 local rc1=0 rc2=0
277 IsIPv4 "$1" || rc1=$?
278 IsIPv4 "$2" || rc2=$?
279 (( rc1 == rc2 ))
280}
281
282# Checks to see if the machine has the provided IP address information
283# already configured. If not, it deletes all of the information for that
284# address family and adds the provided IP address.
285UpdateIP() {
286 local service="$1"
287 local netdev="$2"
288 local ip="$3"
289 local prefix="$4"
290
291 local should_add=1
292 local delete_services=()
293 local delete_objects=()
294 local entry
295 while read entry; do
296 eval "$(echo "$entry" | JSONToVars)" || return $?
297 eval "$(GetIP "$service" "$object" | JSONToVars)" || return $?
298 if [ "$(normalize_ip "$Address")" = "$(normalize_ip "$ip")" ] && \
299 [ "$PrefixLength" = "$prefix" ]; then
300 should_add=0
301 elif MatchingAF "$ip" "$Address"; then
302 echo "Deleting spurious IP: $Address/$PrefixLength" >&2
303 delete_services+=("$service")
304 delete_objects+=("$object")
305 fi
306 done < <(GetIPObjects "$netdev")
307
308 local i
309 for (( i=0; i<${#delete_objects[@]}; ++i )); do
310 DeleteObject "${delete_services[$i]}" "${delete_objects[$i]}" || return $?
311 done
312
313 if (( should_add == 0 )); then
314 echo "Not adding IP: $ip/$prefix" >&2
315 else
316 echo "Adding IP: $ip/$prefix" >&2
317 AddIP "$service" "$netdev" "$ip" "$prefix" || return $?
318 fi
319}
320
321# Sets the system gateway property to the provided IP address if not already
322# set to the current value.
323UpdateGateway() {
324 local service="$1"
325 local ip="$2"
326
327 local object='/xyz/openbmc_project/network/config'
328 local interface='xyz.openbmc_project.Network.SystemConfiguration'
329 local property='DefaultGateway'
330 if ! IsIPv4 "$ip"; then
331 property='DefaultGateway6'
332 fi
333
334 local current_ip
335 current_ip="$(GetProperty "$service" "$object" "$interface" "$property")" || \
336 return $?
337 if [ -n "$current_ip" ] && \
338 [ "$(normalize_ip "$ip")" = "$(normalize_ip "$current_ip")" ]; then
339 echo "Not reconfiguring gateway: $ip" >&2
340 return 0
341 fi
342
343 echo "Setting gateway: $ip" >&2
344 busctl set-property "$service" "$object" "$interface" "$property" s "$ip"
345}
346
347# Checks to see if the machine has the provided neighbor information
348# already configured. If not, it deletes all of the information for that
349# address family and adds the provided neighbor entry.
350UpdateNeighbor() {
351 local service="$1"
352 local netdev="$2"
353 local ip="$3"
354 local mac="$4"
355
356 local should_add=1
357 local delete_services=()
358 local delete_objects=()
359 local entry
360 while read entry; do
361 eval "$(echo "$entry" | JSONToVars)" || return $?
362 eval "$(GetNeighbor "$service" "$object" | JSONToVars)" || return $?
363 if [ "$(normalize_ip "$IPAddress")" = "$(normalize_ip "$ip")" ] && \
364 [ "$(normalize_mac "$MACAddress")" = "$(normalize_mac "$mac")" ]; then
365 should_add=0
366 elif MatchingAF "$ip" "$IPAddress"; then
367 echo "Deleting spurious neighbor: $IPAddress $MACAddress" >&2
368 delete_services+=("$service")
369 delete_objects+=("$object")
370 fi
371 done < <(GetNeighborObjects "$netdev" 2>/dev/null)
372
373 local i
374 for (( i=0; i<${#delete_objects[@]}; ++i )); do
375 DeleteObject "${delete_services[$i]}" "${delete_objects[$i]}" || return $?
376 done
377
378 if (( should_add == 0 )); then
379 echo "Not adding neighbor: $ip $mac" >&2
380 else
381 echo "Adding neighbor: $ip $mac" >&2
382 AddNeighbor "$service" "$netdev" "$ip" "$mac" || return $?
383 fi
384}