From 4e4f9e591a2f07f0f980221ab32afbeca1503c7261b6445a6f3873b4115a9e26 Mon Sep 17 00:00:00 2001 From: Marco Chiappero Date: Thu, 24 Jul 2025 10:08:20 +0000 Subject: [PATCH 01/12] Simplify the setting of host_ip in ironic.conf The value of host_ip is determined twice within the ironic.conf.j2 template file, by means of a relatively hard to read set of conditions. Avoid this duplication and improve readability by exporting the correct value once in scripts/configure-ironic.sh. This also leave more room for more complex evaluations should these be needed in the future. Signed-off-by: Marco Chiappero --- ironic-image/ironic-config/ironic.conf.j2 | 4 ++-- ironic-image/scripts/configure-ironic.sh | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/ironic-image/ironic-config/ironic.conf.j2 b/ironic-image/ironic-config/ironic.conf.j2 index 1201d74..f029a58 100644 --- a/ironic-image/ironic-config/ironic.conf.j2 +++ b/ironic-image/ironic-config/ironic.conf.j2 @@ -65,7 +65,7 @@ port = {{ env.IRONIC_PRIVATE_PORT }} {% endif %} public_endpoint = {{ env.IRONIC_BASE_URL }} {% else %} -host_ip = {% if env.LISTEN_ALL_INTERFACES | lower == "true" %}::{% else %}{{ env.IRONIC_IP }}{% endif %} +host_ip = {{ env.IRONIC_HOST_IP }} port = {{ env.IRONIC_LISTEN_PORT }} {% if env.IRONIC_TLS_SETUP == "true" %} enable_ssl_api = true @@ -181,7 +181,7 @@ cipher_suite_versions = 3,17 # containers are in host networking. auth_strategy = http_basic http_basic_auth_user_file = {{ env.IRONIC_RPC_HTPASSWD_FILE }} -host_ip = {% if env.LISTEN_ALL_INTERFACES | lower == "true" %}::{% else %}{{ env.IRONIC_IP }}{% endif %} +host_ip = {{ env.IRONIC_HOST_IP }} {% if env.IRONIC_TLS_SETUP == "true" %} use_ssl = true cafile = {{ env.IRONIC_CACERT_FILE }} diff --git a/ironic-image/scripts/configure-ironic.sh b/ironic-image/scripts/configure-ironic.sh index 781bf48..acbf4c8 100755 --- a/ironic-image/scripts/configure-ironic.sh +++ b/ironic-image/scripts/configure-ironic.sh @@ -51,6 +51,12 @@ export IRONIC_IPA_COLLECTORS=${IRONIC_IPA_COLLECTORS:-default,logs} wait_for_interface_or_ip +if [[ "$(echo "$LISTEN_ALL_INTERFACES" | tr '[:upper:]' '[:lower:]')" == "true" ]]; then +export IRONIC_HOST_IP="::" +else +export IRONIC_HOST_IP="$IRONIC_IP" +fi + # Hostname to use for the current conductor instance. export IRONIC_CONDUCTOR_HOST=${IRONIC_CONDUCTOR_HOST:-${IRONIC_URL_HOST}} -- 2.49.0 From 60f0bdd5f0114e2cc2dabfb159cc8b512adefd18406066da856c59501c148f6e Mon Sep 17 00:00:00 2001 From: Marco Chiappero Date: Thu, 24 Jul 2025 14:27:27 +0000 Subject: [PATCH 02/12] Remove PROVISIONING_INTERFACE default for better validation Whenever PROVISIONING_INTERFACE is not set by the user, function get_provisioning_interface attempts to determine one, or provide "provisionign" as default value. However this can cause confusing errors down the line. Remove this default value and fail gracefully, with proper logging, if the PROVISIONING_INTERFACE value is not detected. Signed-off-by: Marco Chiappero --- ironic-image/scripts/ironic-common.sh | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ironic-image/scripts/ironic-common.sh b/ironic-image/scripts/ironic-common.sh index b47ff38..e3d3383 100644 --- a/ironic-image/scripts/ironic-common.sh +++ b/ironic-image/scripts/ironic-common.sh @@ -41,7 +41,7 @@ get_provisioning_interface() return fi - local interface="provisioning" + local interface="" if [[ -n "${PROVISIONING_IP}" ]]; then if ip -br addr show | grep -i " ${PROVISIONING_IP}/" &>/dev/null; then @@ -79,13 +79,16 @@ wait_for_interface_or_ip() echo "Waiting for ${IRONIC_IP} to be configured on an interface" sleep 1 done - else + elif [[ -n "${PROVISIONING_INTERFACE}" ]]; then until [[ -n "$IRONIC_IP" ]]; do echo "Waiting for ${PROVISIONING_INTERFACE} interface to be configured" IRONIC_IP="$(ip -br add show scope global up dev "${PROVISIONING_INTERFACE}" | awk '{print $3}' | sed -e 's%/.*%%' | head -n 1)" export IRONIC_IP sleep 1 done + else + echo "Cannot determine an interface or an IP for binding and creating URLs" + return 1 fi # If the IP contains a colon, then it's an IPv6 address, and the HTTP -- 2.49.0 From c69044ff2b379b2846f70579b24fc6fd22e942c87de3b0029c19a1df18222a53 Mon Sep 17 00:00:00 2001 From: Marco Chiappero Date: Wed, 23 Jul 2025 12:04:03 +0000 Subject: [PATCH 03/12] Add two new utility functions for later refactoring The way the ironic-image processes are bound to internet sockets is mainly by PROVISIONING_IP or PROVISIONING_INTERFACE, that is, by looking up a specific address on an interface, or a specific interface for a workable address. Introduce two new utility functions in ironic-common.sh for these two purposes: get_interface_of_ip: returns the name of the interface where the IP address provided as argument is found get_ip_of_interface: returns the first IP associated to the interface provided as argument These two functions will be put into use in subsequent commits. Signed-off-by: Marco Chiappero --- ironic-image/scripts/ironic-common.sh | 57 +++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/ironic-image/scripts/ironic-common.sh b/ironic-image/scripts/ironic-common.sh index e3d3383..298bb7b 100644 --- a/ironic-image/scripts/ironic-common.sh +++ b/ironic-image/scripts/ironic-common.sh @@ -33,6 +33,63 @@ export LOCAL_DB_URI="sqlite:///${IRONIC_DB_DIR}/ironic.sqlite" export IRONIC_USE_MARIADB=${IRONIC_USE_MARIADB:-false} + +get_interface_of_ip() +{ + local IP_VERS="" + + if [[ "$#" -gt 2 ]]; then + echo "${FUNCNAME}: too many parameters" >&2 + return 1 + fi + + if [[ "$#" -eq 2 ]]; then + case $2 in + 4|6) + local IP_VERS="-${2}" + ;; + *) + echo "${FUNCNAME}: the second parameter should be [4|6] (or missing for both)" >&2 + return 2 + ;; + esac + fi + + local IP_ADDR=$1 + + # Convert the address using ipcalc which strips out the subnet. + # For IPv6 addresses, this will give the short-form address + IP_ADDR="$(ipcalc "${IP_ADDR}" | grep "^Address:" | awk '{print $2}')" + + echo "$(ip $IP_VERS -br addr show scope global | grep -i " ${IP_ADDR}/" | cut -f 1 -d ' ' | cut -f 1 -d '@')" +} + +get_ip_of_interface() +{ + local IP_VERS="" + + if [[ "$#" -gt 2 ]]; then + echo "${FUNCNAME}: too many parameters" >&2 + return 1 + fi + + if [[ "$#" -eq 2 ]]; then + case $2 in + 4|6) + local IP_VERS="-${2}" + ;; + *) + echo "${FUNCNAME}: the second parameter should be [4|6] (or missing for both)" >&2 + return 2 + ;; + esac + fi + + local IFACE=$1 + + echo "$(ip $IP_VERS -br addr show scope global up dev $IFACE | awk '{print $3}' | sed -e 's%/.*%%' | head -n 1)" +} + get_provisioning_interface() { if [[ -n "$PROVISIONING_INTERFACE" ]]; then -- 2.49.0 From ca7da400d04f159d0d284d1dcb12349f578379e9f929d1cf6f9080b765f49d75 Mon Sep 17 00:00:00 2001 From: Marco Chiappero Date: Wed, 23 Jul 2025 12:35:49 +0000 Subject: [PATCH 04/12] Leverage get_interface_of_ip to look PROVISIONING_IP up Use the previously introduced get_interface_of_ip, to determine if the PROVISIONING_IP address is actually present on a network interface. This improves the code readability and enables additional debugging output. Signed-off-by: Marco Chiappero --- ironic-image/scripts/ironic-common.sh | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/ironic-image/scripts/ironic-common.sh b/ironic-image/scripts/ironic-common.sh index 298bb7b..4930d07 100644 --- a/ironic-image/scripts/ironic-common.sh +++ b/ironic-image/scripts/ironic-common.sh @@ -128,14 +128,17 @@ wait_for_interface_or_ip() # available on an interface, otherwise we look at $PROVISIONING_INTERFACE # for an IP if [[ -n "${PROVISIONING_IP}" ]]; then - # Convert the address using ipcalc which strips out the subnet. - # For IPv6 addresses, this will give the short-form address - IRONIC_IP="$(ipcalc "${PROVISIONING_IP}" | grep "^Address:" | awk '{print $2}')" - export IRONIC_IP - until grep -F " ${IRONIC_IP}/" <(ip -br addr show); do - echo "Waiting for ${IRONIC_IP} to be configured on an interface" + local IFACE_OF_IP="" + + until [[ -n "$IFACE_OF_IP" ]]; do + echo "Waiting for ${PROVISIONING_IP} to be configured on an interface..." + IFACE_OF_IP="$(get_interface_of_ip $PROVISIONING_IP)" sleep 1 done + + echo "Found $PROVISIONING_IP on interface \"${IFACE_OF_IP}\"!" + + export PROVISIONING_INTERFACE="$IFACE_OF_IP" elif [[ -n "${PROVISIONING_INTERFACE}" ]]; then until [[ -n "$IRONIC_IP" ]]; do echo "Waiting for ${PROVISIONING_INTERFACE} interface to be configured" -- 2.49.0 From 19394a8b03811a0bc4c6a944c17a6b57c57bfe733fdf0487b3bba9a114329559 Mon Sep 17 00:00:00 2001 From: Marco Chiappero Date: Wed, 23 Jul 2025 12:48:45 +0000 Subject: [PATCH 05/12] Revert 2742439 being now redundant Commit 2742439 added logic to tentatively identify the interface name in get_provisioning_interface if the PROVISIONING_IP is provided. However the same process in then repeated in wait_for_interface_or_ip. Signed-off-by: Marco Chiappero --- ironic-image/scripts/ironic-common.sh | 6 ------ 1 file changed, 6 deletions(-) diff --git a/ironic-image/scripts/ironic-common.sh b/ironic-image/scripts/ironic-common.sh index 4930d07..833c0ab 100644 --- a/ironic-image/scripts/ironic-common.sh +++ b/ironic-image/scripts/ironic-common.sh @@ -100,12 +100,6 @@ get_provisioning_interface() local interface="" - if [[ -n "${PROVISIONING_IP}" ]]; then - if ip -br addr show | grep -i " ${PROVISIONING_IP}/" &>/dev/null; then - interface="$(ip -br addr show | grep -i " ${PROVISIONING_IP}/" | cut -f 1 -d ' ' | cut -f 1 -d '@')" - fi - fi - for mac in ${PROVISIONING_MACS//,/ }; do if ip -br link show up | grep -i "$mac" &>/dev/null; then interface="$(ip -br link show up | grep -i "$mac" | cut -f 1 -d ' ' | cut -f 1 -d '@')" -- 2.49.0 From d59126b517db13eb4d7935d832e6154901c50421a1562530126ba703d15c9caa Mon Sep 17 00:00:00 2001 From: Marco Chiappero Date: Wed, 23 Jul 2025 12:25:54 +0000 Subject: [PATCH 06/12] Introduce IRONIC_IPV6 to bind on IPv6 sockets The ironic scripts either use PROVISIONING_IP as an input or try to determine an IP address to bind the sockets to. This results in IRONIC_IP being defined once the process is complete, and it can carry either an IPv4 or an IPv6 address. Likely, the assumption is that on Linux, by default, IPv4-mapped IPv6 addresses can be leveraged to serve both IPv4 and IPv6 through a single socket. However this is not a good practice and two separate sockets should be used instead, whenever possible. This change modifies such logic by - introducing the variable IRONIC_IPV6 alongside the existing - matching IRONIC_IP and attempting to populate both variables Please note that hostname based URLs, with both A and AAAA records, are also required for a fully working dual-stack configuration. Signed-off-by: Marco Chiappero --- ironic-image/scripts/configure-ironic.sh | 9 +++++- ironic-image/scripts/ironic-common.sh | 41 +++++++++++++++++------- 2 files changed, 38 insertions(+), 12 deletions(-) diff --git a/ironic-image/scripts/configure-ironic.sh b/ironic-image/scripts/configure-ironic.sh index acbf4c8..b32d497 100755 --- a/ironic-image/scripts/configure-ironic.sh +++ b/ironic-image/scripts/configure-ironic.sh @@ -98,4 +98,11 @@ render_j2_config "/etc/ironic/ironic.conf.j2" \ configure_json_rpc_auth # Make sure ironic traffic bypasses any proxies -export NO_PROXY="${NO_PROXY:-},$IRONIC_IP" +export NO_PROXY="${NO_PROXY:-}" + +if [[ -n "$IRONIC_IPV6" ]]; then +export NO_PROXY="${NO_PROXY},${IRONIC_IPV6}" +fi +if [[ -n "$IRONIC_IP" ]]; then +export NO_PROXY="${NO_PROXY},${IRONIC_IP}" +fi diff --git a/ironic-image/scripts/ironic-common.sh b/ironic-image/scripts/ironic-common.sh index 833c0ab..0a9e76b 100644 --- a/ironic-image/scripts/ironic-common.sh +++ b/ironic-image/scripts/ironic-common.sh @@ -5,6 +5,7 @@ set -euxo pipefail # Export IRONIC_IP to avoid needing to lean on IRONIC_URL_HOST for consumption in # e.g. dnsmasq configuration export IRONIC_IP="${IRONIC_IP:-}" +export IRONIC_IPV6="${IRONIC_IPV6:-}" PROVISIONING_INTERFACE="${PROVISIONING_INTERFACE:-}" PROVISIONING_IP="${PROVISIONING_IP:-}" PROVISIONING_MACS="${PROVISIONING_MACS:-}" @@ -133,27 +134,45 @@ wait_for_interface_or_ip() echo "Found $PROVISIONING_IP on interface \"${IFACE_OF_IP}\"!" export PROVISIONING_INTERFACE="$IFACE_OF_IP" + # If the IP contains a colon, then it's an IPv6 address + if [[ "$PROVISIONING_IP" =~ .*:.* ]]; then + export IRONIC_IPV6="$PROVISIONING_IP" + else + export IRONIC_IP="$PROVISIONING_IP" + fi elif [[ -n "${PROVISIONING_INTERFACE}" ]]; then - until [[ -n "$IRONIC_IP" ]]; do - echo "Waiting for ${PROVISIONING_INTERFACE} interface to be configured" - IRONIC_IP="$(ip -br add show scope global up dev "${PROVISIONING_INTERFACE}" | awk '{print $3}' | sed -e 's%/.*%%' | head -n 1)" - export IRONIC_IP + until [[ -n "$IRONIC_IPV6" ]] || [[ -n "$IRONIC_IP" ]]; do + echo "Waiting for ${PROVISIONING_INTERFACE} interface to be configured..." + + export IRONIC_IPV6="$(get_ip_of_interface $PROVISIONING_INTERFACE 6)" + sleep 1 + + export IRONIC_IP="$(get_ip_of_interface $PROVISIONING_INTERFACE 4)" sleep 1 done + + # Add some debugging output + if [[ -n "$IRONIC_IPV6" ]]; then + echo "Found $IRONIC_IPV6 on interface \"${PROVISIONING_INTERFACE}\"!" + fi + if [[ -n "$IRONIC_IP" ]]; then + echo "Found $IRONIC_IP on interface \"${PROVISIONING_INTERFACE}\"!" + fi else echo "Cannot determine an interface or an IP for binding and creating URLs" return 1 fi - # If the IP contains a colon, then it's an IPv6 address, and the HTTP - # host needs surrounding with brackets - if [[ "$IRONIC_IP" =~ .*:.* ]]; then - export IPV=6 - export IRONIC_URL_HOST="[$IRONIC_IP]" - else - export IPV=4 + # Define the URLs based on the what we have found, + # prioritize IPv6 for IRONIC_URL_HOST + if [[ -n "$IRONIC_IP" ]]; then + export ENABLE_IPV4=yes export IRONIC_URL_HOST="$IRONIC_IP" fi + if [[ -n "$IRONIC_IPV6" ]]; then + export ENABLE_IPV6=yes + export IRONIC_URL_HOST="[$IRONIC_IPV6]" # The HTTP host needs surrounding with brackets + fi # Avoid having to construct full URL multiple times while allowing # the override of IRONIC_HTTP_URL for environments in which IRONIC_IP -- 2.49.0 From ad01fecc4f69b4929ad201d755e75d36ecf0ad6dcbfe06b88316b292763cad52 Mon Sep 17 00:00:00 2001 From: Marco Chiappero Date: Wed, 23 Jul 2025 12:39:20 +0000 Subject: [PATCH 07/12] Allow binding on the provisioning network via a hostname In a dual-stack scenario, especially when deploying in direct mode via virtual media, it might be useful to 1) use a hostname to enable "dual IP" URLs 2) have ironic bind to those two addresses, if found on the system. To make this possible, this commit introduces: - a new user environment variable named IRONIC_URL_HOSTNAME, to be used as immutable external only input, to derive IRONIC_URL_HOST and the IP addresses to bind on - a new utility function named "get_ip_of_hostname" to help look up the A and AAAA records - additional logic to look for the returned address on the system, for binding the processes; this new logic has lower priority than PROVISIONING_IP (which can then be used to enforce one specific IP version) and PROVISIONING_INTERFACE Note, while IRONIC_URL_HOSTNAME and PROVISIONING_IP are considered to be mutually exclusive, IRONIC_URL_HOSTNAME and PROVISIONING_INTERFACE are not. Signed-off-by: Marco Chiappero --- ironic-image/Dockerfile | 4 +- ironic-image/scripts/ironic-common.sh | 72 ++++++++++++++++++++++++++- 2 files changed, 73 insertions(+), 3 deletions(-) diff --git a/ironic-image/Dockerfile b/ironic-image/Dockerfile index d785c6d..4d3f5c9 100644 --- a/ironic-image/Dockerfile +++ b/ironic-image/Dockerfile @@ -19,11 +19,11 @@ RUN sed -i -e 's%^# rpm.install.excludedocs = no.*%rpm.install.excludedocs = yes #!ArchExclusiveLine: x86_64 RUN if [ "$(uname -m)" = "x86_64" ];then \ - zypper --installroot /installroot --non-interactive install --no-recommends syslinux python311-devel python311 python311-pip python311-sushy-oem-idrac python311-proliantutils python311-sushy python311-pyinotify python3-ironicclient git curl sles-release tar gzip vim gawk dnsmasq dosfstools apache2 apache2-mod_wsgi ipcalc ipmitool iproute2 procps qemu-tools sqlite3 util-linux xorriso tftp ipxe-bootimgs python311-sushy-tools crudini openstack-ironic; \ + zypper --installroot /installroot --non-interactive install --no-recommends syslinux python311-devel python311 python311-pip python311-sushy-oem-idrac python311-proliantutils python311-sushy python311-pyinotify python3-ironicclient git curl sles-release tar gzip vim gawk dnsmasq dosfstools apache2 apache2-mod_wsgi ipcalc ipmitool iproute2 bind-utils procps qemu-tools sqlite3 util-linux xorriso tftp ipxe-bootimgs python311-sushy-tools crudini openstack-ironic; \ fi #!ArchExclusiveLine: aarch64 RUN if [ "$(uname -m)" = "aarch64" ];then \ - zypper --installroot /installroot --non-interactive install --no-recommends python311-devel python311 python311-pip python311-sushy-oem-idrac python311-proliantutils python311-sushy python311-pyinotify python3-ironicclient git curl sles-release tar gzip vim gawk dnsmasq dosfstools apache2 apache2-mod_wsgi ipcalc ipmitool iproute2 procps qemu-tools sqlite3 util-linux xorriso tftp ipxe-bootimgs python311-sushy-tools crudini openstack-ironic; \ + zypper --installroot /installroot --non-interactive install --no-recommends python311-devel python311 python311-pip python311-sushy-oem-idrac python311-proliantutils python311-sushy python311-pyinotify python3-ironicclient git curl sles-release tar gzip vim gawk dnsmasq dosfstools apache2 apache2-mod_wsgi ipcalc ipmitool iproute2 bind-utils procps qemu-tools sqlite3 util-linux xorriso tftp ipxe-bootimgs python311-sushy-tools crudini openstack-ironic; \ fi # DATABASE diff --git a/ironic-image/scripts/ironic-common.sh b/ironic-image/scripts/ironic-common.sh index 0a9e76b..cf606a6 100644 --- a/ironic-image/scripts/ironic-common.sh +++ b/ironic-image/scripts/ironic-common.sh @@ -9,6 +9,7 @@ export IRONIC_IPV6="${IRONIC_IPV6:-}" PROVISIONING_INTERFACE="${PROVISIONING_INTERFACE:-}" PROVISIONING_IP="${PROVISIONING_IP:-}" PROVISIONING_MACS="${PROVISIONING_MACS:-}" +IRONIC_URL_HOSTNAME="${IRONIC_URL_HOSTNAME:-}" IPXE_CUSTOM_FIRMWARE_DIR="${IPXE_CUSTOM_FIRMWARE_DIR:-/shared/custom_ipxe_firmware}" CUSTOM_CONFIG_DIR="${CUSTOM_CONFIG_DIR:-/conf}" CUSTOM_DATA_DIR="${CUSTOM_DATA_DIR:-/data}" @@ -35,6 +36,28 @@ export LOCAL_DB_URI="sqlite:///${IRONIC_DB_DIR}/ironic.sqlite" export IRONIC_USE_MARIADB=${IRONIC_USE_MARIADB:-false} +get_ip_of_hostname() +{ + if [[ "$#" -ne 2 ]]; then + echo "${FUNCNAME}: two parameters required, $# provided" >&2 + return 1 + fi + + case $2 in + 4) + QUERY="a";; + 6) + QUERY="aaaa";; + *) + echo "${FUNCNAME}: the second parameter should be [a|aaaa] for A and AAAA records" + return 1;; + esac + + local HOSTNAME=$1 + + echo "$(nslookup -type=${QUERY} $HOSTNAME | tail -n2 | grep -w "Address:" | cut -d " " -f2)" +} + get_interface_of_ip() { local IP_VERS="" @@ -151,13 +174,55 @@ wait_for_interface_or_ip() sleep 1 done - # Add some debugging output if [[ -n "$IRONIC_IPV6" ]]; then echo "Found $IRONIC_IPV6 on interface \"${PROVISIONING_INTERFACE}\"!" fi if [[ -n "$IRONIC_IP" ]]; then echo "Found $IRONIC_IP on interface \"${PROVISIONING_INTERFACE}\"!" fi + elif [[ -n "$IRONIC_URL_HOSTNAME" ]]; then + local IPV6_IFACE="" + local IPV4_IFACE="" + + # we should get at least one IP address + until [[ -n "$IPV6_IFACE" ]] || [[ -n "$IPV4_IFACE" ]]; do + local IPV6_RECORD="" + local IPV4_RECORD="" + + IPV6_RECORD="$(get_ip_of_hostname $IRONIC_URL_HOSTNAME 6)" + IPV4_RECORD="$(get_ip_of_hostname $IRONIC_URL_HOSTNAME 4)" + + # We couldn't get any IP + if [[ -z "$IPV4_RECORD" ]] && [[ -z "$IPV6_RECORD" ]]; then + echo "${FUNCNAME}: no valid IP found for hostname $IRONIC_URL_HOSTNAME" >&2 + return 1 + fi + + echo "Waiting for ${IPV6_RECORD} to be configured on an interface" + IPV6_IFACE="$(get_interface_of_ip $IPV6_RECORD 6)" + sleep 1 + + echo "Waiting for ${IPV4_RECORD} to be configured on an interface" + IPV4_IFACE="$(get_interface_of_ip $IPV4_RECORD 4)" + sleep 1 + done + + # Add some debugging output + if [[ -n "$IPV6_IFACE" ]]; then + echo "Found $IPV6_RECORD on interface \"${IPV6_IFACE}\"!" + export IRONIC_IPV6="$IPV6_RECORD" + fi + if [[ -n "$IPV4_IFACE" ]]; then + echo "Found $IPV4_RECORD on interface \"${IPV4_IFACE}\"!" + export IRONIC_IP="$IPV4_RECORD" + fi + + # Make sure both IPs are asigned to the same interface + if [[ -n "$IPV6_IFACE" ]] && [[ -n "$IPV4_IFACE" ]] && [[ "$IPV6_IFACE" != "$IPV4_IFACE" ]]; then + echo "Warning, the IPv4 and IPv6 addresses from \"${HOSTNAME}\" are assigned to different " \ + "interfaces (\"${IPV6_IFACE}\" and \"${IPV4_IFACE}\")" >&2 + fi + else echo "Cannot determine an interface or an IP for binding and creating URLs" return 1 @@ -174,6 +239,11 @@ wait_for_interface_or_ip() export IRONIC_URL_HOST="[$IRONIC_IPV6]" # The HTTP host needs surrounding with brackets fi + # Once determined if we have IPv4 and/or IPv6, override the hostname if provided + if [[ -n "$IRONIC_URL_HOSTNAME" ]]; then + IRONIC_URL_HOST=$IRONIC_URL_HOSTNAME + fi + # Avoid having to construct full URL multiple times while allowing # the override of IRONIC_HTTP_URL for environments in which IRONIC_IP # is unreachable from hosts being provisioned. -- 2.49.0 From a94cde2a35b3780a82c409f8937e35b4374fee9a8e969d8f0e367df39880c1d5 Mon Sep 17 00:00:00 2001 From: Marco Chiappero Date: Wed, 23 Jul 2025 20:47:00 +0000 Subject: [PATCH 08/12] Use my_ipv6 when IRONIC_IPV6 is defined in ironic.conf As per the Ironic documentation: "This field [my_ip] does accept an IPv6 address as an override for templates and URLs, however it is recommended that [DEFAULT]my_ipv6 is used along with DNS names for service URLs for dual-stack environments." Fill my_ipv6 when an IPv6 address has been found for binding. Signed-off-by: Marco Chiappero --- ironic-image/ironic-config/ironic.conf.j2 | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ironic-image/ironic-config/ironic.conf.j2 b/ironic-image/ironic-config/ironic.conf.j2 index f029a58..7333c37 100644 --- a/ironic-image/ironic-config/ironic.conf.j2 +++ b/ironic-image/ironic-config/ironic.conf.j2 @@ -25,7 +25,13 @@ rpc_transport = none use_stderr = true # NOTE(dtantsur): the default md5 is not compatible with FIPS mode hash_ring_algorithm = sha256 +{% if env.ENABLE_IPV4 %} my_ip = {{ env.IRONIC_IP }} +{% endif %} +{% if env.ENABLE_IPV6 %} +my_ipv6 = {{ env.IRONIC_IPV6 }} +{% endif %} + host = {{ env.IRONIC_CONDUCTOR_HOST }} tempdir = {{ env.IRONIC_TMP_DATA_DIR }} -- 2.49.0 From 582aaaa424a9111f49b3900750baa78817c24d6852f21cc9eaa9a6393a9b5633 Mon Sep 17 00:00:00 2001 From: Marco Chiappero Date: Thu, 24 Jul 2025 10:08:20 +0000 Subject: [PATCH 09/12] Set host_ip to an IPv6 address when found Prioritize IPv6 over IPv4 when available to set host_ip in ironic.conf when LISTEN_ALL_INTERFACES is not set to true. Signed-off-by: Marco Chiappero --- ironic-image/scripts/configure-ironic.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ironic-image/scripts/configure-ironic.sh b/ironic-image/scripts/configure-ironic.sh index b32d497..3baf1f1 100755 --- a/ironic-image/scripts/configure-ironic.sh +++ b/ironic-image/scripts/configure-ironic.sh @@ -53,6 +53,8 @@ wait_for_interface_or_ip if [[ "$(echo "$LISTEN_ALL_INTERFACES" | tr '[:upper:]' '[:lower:]')" == "true" ]]; then export IRONIC_HOST_IP="::" +elif [[ -n env.ENABLE_IPV6 ]]; then +export IRONIC_HOST_IP="$IRONIC_IPV6" else export IRONIC_HOST_IP="$IRONIC_IP" fi -- 2.49.0 From fc0cfda2c05585553db9073fc9761765ebc56d18ba6548fd6635a83661040efd Mon Sep 17 00:00:00 2001 From: Marco Chiappero Date: Wed, 23 Jul 2025 21:41:53 +0000 Subject: [PATCH 10/12] Let Ironic API use IPv4 and IPv6 sockets when possible When LISTEN_ALL_INTERFACES is not set, Apache should make Ironic API avaiable on either or both IPv4 and IPv6 sockets, depending on the addresses requested or found on the system. Make sure to set the "Listen" directive according to ENABLE_IPV4 and ENABLE_IPV4, and the VirtualHost when IRONIC_URL_HOSTNAME is present. Signed-off-by: Marco Chiappero --- ironic-image/ironic-config/httpd-ironic-api.conf.j2 | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/ironic-image/ironic-config/httpd-ironic-api.conf.j2 b/ironic-image/ironic-config/httpd-ironic-api.conf.j2 index 317118e..47da2ae 100644 --- a/ironic-image/ironic-config/httpd-ironic-api.conf.j2 +++ b/ironic-image/ironic-config/httpd-ironic-api.conf.j2 @@ -15,8 +15,17 @@ Listen {{ env.IRONIC_LISTEN_PORT }} {% else %} -Listen {{ env.IRONIC_URL_HOST }}:{{ env.IRONIC_LISTEN_PORT }} - +{% if env.ENABLE_IPV4 %} +Listen {{ env.IRONIC_IP }}:{{ env.IRONIC_LISTEN_PORT }} +{% endif %} +{% if env.ENABLE_IPV6 %} +Listen [{{ env.IRONIC_IPV6 }}]:{{ env.IRONIC_LISTEN_PORT }} +{% endif %} +{% if env.IRONIC_URL_HOSTNAME is defined and env.IRONIC_URL_HOSTNAME|length %} + +{% else %} + +{% endif %} {% endif %} {% if env.IRONIC_PRIVATE_PORT == "unix" %} -- 2.49.0 From eecd30e90d02da48c280a84d3c7f1841f4f006026e37d0ec7c8ec57545e3fd12 Mon Sep 17 00:00:00 2001 From: Marco Chiappero Date: Mon, 28 Jul 2025 09:00:17 +0000 Subject: [PATCH 11/12] Update httpd.conf to bind to IPv4 and/or IPv6 sockets Enable the use of individual IPv4 and IPv6 sockets when the respective IP is detected and LISTEN_ALL_INTERFACES is not set to true. This allows to correctly bind to both the IPv4 and IPv6 addresses found and not just one of them. Signed-off-by: Marco Chiappero --- ironic-image/ironic-config/httpd.conf.j2 | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ironic-image/ironic-config/httpd.conf.j2 b/ironic-image/ironic-config/httpd.conf.j2 index ef3de62..74af4c8 100644 --- a/ironic-image/ironic-config/httpd.conf.j2 +++ b/ironic-image/ironic-config/httpd.conf.j2 @@ -2,7 +2,12 @@ ServerRoot {{ env.HTTPD_DIR }} {%- if env.LISTEN_ALL_INTERFACES | lower == "true" %} Listen {{ env.HTTP_PORT }} {% else %} -Listen {{ env.IRONIC_URL_HOST }}:{{ env.HTTP_PORT }} +{% if env.ENABLE_IPV4 %} +Listen {{ env.IRONIC_IP }}:{{ env.HTTP_PORT }} +{% endif %} +{% if env.ENABLE_IPV6 %} +Listen [{{ env.IRONIC_IPV6 }}]:{{ env.HTTP_PORT }} +{% endif %} {% endif %} Include /etc/httpd/conf.modules.d/*.conf User ironic-suse -- 2.49.0 From e2d38a867c40c261b6066c9bf570b40b767327f9342adb0b686199aa651f371b Mon Sep 17 00:00:00 2001 From: Marco Chiappero Date: Wed, 23 Jul 2025 21:44:10 +0000 Subject: [PATCH 12/12] Let Apache use separate IPv4 and IPv6 sockets for listening to any Enable the use of two separate sockets for IPv4 and IPv6 when LISTEN_ALL_INTERFACES is set to true. While desirable, on Linux Apache uses IPv4-mapped IPv6 addresses by default, thus leveraging a single IPv6 socket for IPv4 connections as well. This behaviour is far from being desirable and can be disabled at compile time via the "--disable-v4-mapped" flag, so make sure both an ANY address Listen directive is present for both IPv4 and IPv6. When Apache is compiled with "--enable-v4-mapped", the IPv4 socket will be simply ignored. Please see https://httpd.apache.org/docs/2.4/bind.html for more information. Signed-off-by: Marco Chiappero --- ironic-image/ironic-config/apache2-ipxe.conf.j2 | 3 ++- ironic-image/ironic-config/apache2-vmedia.conf.j2 | 3 ++- ironic-image/ironic-config/httpd-ironic-api.conf.j2 | 3 ++- ironic-image/ironic-config/httpd.conf.j2 | 3 ++- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/ironic-image/ironic-config/apache2-ipxe.conf.j2 b/ironic-image/ironic-config/apache2-ipxe.conf.j2 index 88959ff..ae19139 100644 --- a/ironic-image/ironic-config/apache2-ipxe.conf.j2 +++ b/ironic-image/ironic-config/apache2-ipxe.conf.j2 @@ -1,4 +1,5 @@ -Listen {{ env.IPXE_TLS_PORT }} +Listen 0.0.0.0:{{ env.IPXE_TLS_PORT }} +Listen [::]:{{ env.IPXE_TLS_PORT }} ErrorLog /dev/stderr diff --git a/ironic-image/ironic-config/apache2-vmedia.conf.j2 b/ironic-image/ironic-config/apache2-vmedia.conf.j2 index 20dc4a7..c79a5e6 100644 --- a/ironic-image/ironic-config/apache2-vmedia.conf.j2 +++ b/ironic-image/ironic-config/apache2-vmedia.conf.j2 @@ -1,4 +1,5 @@ -Listen {{ env.VMEDIA_TLS_PORT }} +Listen 0.0.0.0:{{ env.VMEDIA_TLS_PORT }} +Listen [::]:{{ env.VMEDIA_TLS_PORT }} ErrorLog /dev/stderr diff --git a/ironic-image/ironic-config/httpd-ironic-api.conf.j2 b/ironic-image/ironic-config/httpd-ironic-api.conf.j2 index 47da2ae..ba28640 100644 --- a/ironic-image/ironic-config/httpd-ironic-api.conf.j2 +++ b/ironic-image/ironic-config/httpd-ironic-api.conf.j2 @@ -12,7 +12,8 @@ {% if env.LISTEN_ALL_INTERFACES | lower == "true" %} -Listen {{ env.IRONIC_LISTEN_PORT }} +Listen 0.0.0.0:{{ env.IRONIC_LISTEN_PORT }} +Listen [::]:{{ env.IRONIC_LISTEN_PORT }} {% else %} {% if env.ENABLE_IPV4 %} diff --git a/ironic-image/ironic-config/httpd.conf.j2 b/ironic-image/ironic-config/httpd.conf.j2 index 74af4c8..7c7106d 100644 --- a/ironic-image/ironic-config/httpd.conf.j2 +++ b/ironic-image/ironic-config/httpd.conf.j2 @@ -1,6 +1,7 @@ ServerRoot {{ env.HTTPD_DIR }} {%- if env.LISTEN_ALL_INTERFACES | lower == "true" %} -Listen {{ env.HTTP_PORT }} +Listen 0.0.0.0:{{ env.HTTP_PORT }} +Listen [::]:{{ env.HTTP_PORT }} {% else %} {% if env.ENABLE_IPV4 %} Listen {{ env.IRONIC_IP }}:{{ env.HTTP_PORT }} -- 2.49.0