1# 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 15# 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} 399