From 6cfc99cd902aeca83b6212d78b83310483a487ed Mon Sep 17 00:00:00 2001 From: Jan Vaclav Date: Wed, 23 Apr 2025 14:10:23 +0200 Subject: [PATCH] cloud-setup/azure: add support for configuring ipv6 addresses --- src/nm-cloud-setup/main.c | 7 +- src/nm-cloud-setup/nmcs-provider-azure.c | 139 +++++++++++++++++------ src/tests/client/test-client.py | 10 +- 3 files changed, 116 insertions(+), 40 deletions(-) diff --git a/src/nm-cloud-setup/main.c b/src/nm-cloud-setup/main.c index f27226a8bf..9b4f43f677 100644 --- a/src/nm-cloud-setup/main.c +++ b/src/nm-cloud-setup/main.c @@ -581,13 +581,16 @@ _nmc_mangle_connection(NMDevice *device, } if (s_ip6) { - const char *method = nm_setting_ip_config_get_method(s_ip6); + const char *method = nm_setting_ip_config_get_method(s_ip6); + bool needs_auto = nm_streq0("azure", nmcs_provider_get_name(config_data->provider)); + if (NM_IN_STRSET(method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE, NM_SETTING_IP6_CONFIG_METHOD_DISABLED)) { g_object_set(s_ip6, NM_SETTING_IP_CONFIG_METHOD, - NM_SETTING_IP6_CONFIG_METHOD_MANUAL, + needs_auto ? NM_SETTING_IP6_CONFIG_METHOD_AUTO + : NM_SETTING_IP6_CONFIG_METHOD_MANUAL, NULL); } diff --git a/src/nm-cloud-setup/nmcs-provider-azure.c b/src/nm-cloud-setup/nmcs-provider-azure.c index d4999b5f48..1a6745b5d9 100644 --- a/src/nm-cloud-setup/nmcs-provider-azure.c +++ b/src/nm-cloud-setup/nmcs-provider-azure.c @@ -37,6 +37,12 @@ G_DEFINE_TYPE(NMCSProviderAzure, nmcs_provider_azure, NMCS_TYPE_PROVIDER); /*****************************************************************************/ +static gboolean +accept_not_found(long response_code, GBytes *, gpointer, GError **) +{ + return NM_IN_SET(response_code, 200, 404); +} + static void _detect_get_meta_data_done_cb(GObject *source, GAsyncResult *result, gpointer user_data) { @@ -90,6 +96,7 @@ detect(NMCSProvider *provider, GTask *task) typedef enum { GET_CONFIG_FETCH_TYPE_IPV4_IPADDRESS_X_PRIVATEIPADDRESS, + GET_CONFIG_FETCH_TYPE_IPV6_IPADDRESS_X_PRIVATEIPADDRESS, GET_CONFIG_FETCH_TYPE_IPV4_SUBNET_0_ADDRESS, GET_CONFIG_FETCH_TYPE_IPV4_SUBNET_0_PREFIX, } GetConfigFetchType; @@ -102,9 +109,15 @@ typedef struct { guint n_iface_data_pending; } AzureIfaceData; +typedef struct { + AzureIfaceData *data; + int protocol; +} AzureIfaceReqData; + typedef struct { AzureIfaceData *iface_data; guint64 ipaddress_idx; + int protocol; } AzureIpAddressReqData; static void @@ -128,6 +141,7 @@ _get_config_fetch_done_cb(NMHttpClient *http_client, gsize resp_len; char tmp_addr_str[NM_INET_ADDRSTRLEN]; in_addr_t tmp_addr; + struct in6_addr tmp_addr6; int tmp_prefix = -1; nm_http_client_poll_req_finish(http_client, result, NULL, &response, &error); @@ -148,15 +162,27 @@ _get_config_fetch_done_cb(NMHttpClient *http_client, case GET_CONFIG_FETCH_TYPE_IPV4_IPADDRESS_X_PRIVATEIPADDRESS: if (!nmcs_utils_ipaddr_normalize_bin(AF_INET, resp_str, resp_len, NULL, &tmp_addr)) { - error = - nm_utils_error_new(NM_UTILS_ERROR_UNKNOWN, "ip is not a valid private ip address"); + error = nm_utils_error_new(NM_UTILS_ERROR_UNKNOWN, + "ip is not a valid private ipv4 address"); goto out_done; } - _LOGD("interface[%" G_GSSIZE_FORMAT "]: received address %s", + _LOGD("interface[%" G_GSSIZE_FORMAT "]: received ipv4 address %s", iface_data->intern_iface_idx, nm_inet4_ntop(tmp_addr, tmp_addr_str)); iface_get_config->ipv4s_arr[ipaddress_idx] = tmp_addr; break; + case GET_CONFIG_FETCH_TYPE_IPV6_IPADDRESS_X_PRIVATEIPADDRESS: + + if (!nmcs_utils_ipaddr_normalize_bin(AF_INET6, resp_str, resp_len, NULL, &tmp_addr6)) { + error = nm_utils_error_new(NM_UTILS_ERROR_UNKNOWN, + "ip is not a valid private ipv6 address"); + goto out_done; + } + _LOGD("interface[%" G_GSSIZE_FORMAT "]: received ipv6 address %s", + iface_data->intern_iface_idx, + nm_inet6_ntop(&tmp_addr6, tmp_addr_str)); + iface_get_config->ipv6s_arr[ipaddress_idx] = tmp_addr6; + break; case GET_CONFIG_FETCH_TYPE_IPV4_SUBNET_0_ADDRESS: @@ -203,7 +229,7 @@ out_done: } static void -_get_config_fetch_done_cb_ipv4_ipaddress_x_privateipaddress(GObject *source, +_get_config_fetch_done_cb_ipvx_ipaddress_x_privateipaddress(GObject *source, GAsyncResult *result, gpointer user_data) { @@ -212,7 +238,9 @@ _get_config_fetch_done_cb_ipv4_ipaddress_x_privateipaddress(GObject *source _get_config_fetch_done_cb(NM_HTTP_CLIENT(source), result, ipaddress_req_data->iface_data, - GET_CONFIG_FETCH_TYPE_IPV4_IPADDRESS_X_PRIVATEIPADDRESS, + ipaddress_req_data->protocol == 4 + ? GET_CONFIG_FETCH_TYPE_IPV4_IPADDRESS_X_PRIVATEIPADDRESS + : GET_CONFIG_FETCH_TYPE_IPV6_IPADDRESS_X_PRIVATEIPADDRESS, ipaddress_req_data->ipaddress_idx); g_free(ipaddress_req_data); } @@ -245,20 +273,34 @@ static void _get_config_ips_prefix_list_cb(GObject *source, GAsyncResult *result, gpointer user_data) { gs_unref_bytes GBytes *response = NULL; - AzureIfaceData *iface_data = user_data; + AzureIfaceReqData *req_data = user_data; + AzureIfaceData *iface_data = req_data->data; gs_free_error GError *error = NULL; const char *response_str = NULL; + int ip_proto = req_data->protocol; + long response_code; gsize response_len; NMCSProviderGetConfigTaskData *get_config_data; const char *line; gsize line_len; - char iface_idx_str[30]; + int addr_count = 0; + char iface_url_prefix[64]; - nm_http_client_poll_req_finish(NM_HTTP_CLIENT(source), result, NULL, &response, &error); + nm_clear_g_free(&req_data); + nm_http_client_poll_req_finish(NM_HTTP_CLIENT(source), + result, + &response_code, + &response, + &error); if (nm_utils_error_is_cancelled(error)) return; + // We don't have IP addresses of this type, + // so let's skip trying to parse them. + if (response_code == 404) + return; + get_config_data = iface_data->get_config_data; if (error) @@ -268,11 +310,19 @@ _get_config_ips_prefix_list_cb(GObject *source, GAsyncResult *result, gpointer u /* NMHttpClient guarantees that there is a trailing NUL after the data. */ nm_assert(response_str[response_len] == 0); - nm_assert(!iface_data->iface_get_config->ipv4s_arr); - nm_assert(!iface_data->iface_get_config->has_ipv4s); - nm_assert(!iface_data->iface_get_config->has_cidr); + if (ip_proto == 4) { + nm_assert(!iface_data->iface_get_config->ipv4s_arr); + nm_assert(!iface_data->iface_get_config->has_ipv4s); + nm_assert(!iface_data->iface_get_config->has_cidr); + } else { + nm_assert(!iface_data->iface_get_config->ipv6s_arr); + nm_assert(!iface_data->iface_get_config->has_ipv6s); + } - nm_sprintf_buf(iface_idx_str, "%" G_GSSIZE_FORMAT, iface_data->intern_iface_idx); + nm_sprintf_buf(iface_url_prefix, + "%" G_GSSIZE_FORMAT "/ipv%d", + iface_data->intern_iface_idx, + ip_proto); while (nm_utils_parse_next_line(&response_str, &response_len, &line, &line_len)) { AzureIpAddressReqData *ipaddress_req_data; @@ -298,12 +348,15 @@ _get_config_ips_prefix_list_cb(GObject *source, GAsyncResult *result, gpointer u ipaddress_req_data = g_new(AzureIpAddressReqData, 1); ipaddress_req_data->iface_data = iface_data; ipaddress_req_data->ipaddress_idx = ips_prefix_idx; + ipaddress_req_data->protocol = ip_proto; iface_data->n_iface_data_pending++; + addr_count++; + nm_http_client_poll_req( NM_HTTP_CLIENT(source), - (uri = _azure_uri_interfaces(iface_idx_str, - "/ipv4/ipAddress/", + (uri = _azure_uri_interfaces(iface_url_prefix, + "/ipAddress/", nm_sprintf_buf(buf, "%" G_GINT64_FORMAT, ips_prefix_idx), "/privateIpAddress")), HTTP_TIMEOUT_MS, @@ -315,21 +368,21 @@ _get_config_ips_prefix_list_cb(GObject *source, GAsyncResult *result, gpointer u get_config_data->intern_cancellable, NULL, NULL, - _get_config_fetch_done_cb_ipv4_ipaddress_x_privateipaddress, + _get_config_fetch_done_cb_ipvx_ipaddress_x_privateipaddress, ipaddress_req_data); } - iface_data->iface_get_config->ipv4s_arr = g_new(in_addr_t, iface_data->n_iface_data_pending); - iface_data->iface_get_config->has_ipv4s = TRUE; - iface_data->iface_get_config->ipv4s_len = iface_data->n_iface_data_pending; - - { + if (ip_proto == 4 && addr_count) { gs_free char *uri = NULL; + iface_data->iface_get_config->ipv4s_arr = g_new(in_addr_t, addr_count); + iface_data->iface_get_config->has_ipv4s = TRUE; + iface_data->iface_get_config->ipv4s_len = addr_count; + iface_data->n_iface_data_pending++; nm_http_client_poll_req( NM_HTTP_CLIENT(source), - (uri = _azure_uri_interfaces(iface_idx_str, "/ipv4/subnet/0/address/")), + (uri = _azure_uri_interfaces(iface_url_prefix, "/subnet/0/address/")), HTTP_TIMEOUT_MS, 512 * 1024, 10000, @@ -347,7 +400,7 @@ _get_config_ips_prefix_list_cb(GObject *source, GAsyncResult *result, gpointer u iface_data->n_iface_data_pending++; nm_http_client_poll_req( NM_HTTP_CLIENT(source), - (uri = _azure_uri_interfaces(iface_idx_str, "/ipv4/subnet/0/prefix/")), + (uri = _azure_uri_interfaces(iface_url_prefix, "/subnet/0/prefix/")), HTTP_TIMEOUT_MS, 512 * 1024, 10000, @@ -359,6 +412,10 @@ _get_config_ips_prefix_list_cb(GObject *source, GAsyncResult *result, gpointer u NULL, _get_config_fetch_done_cb_ipv4_subnet_0_prefix, iface_data); + } else if (ip_proto == 6 && addr_count) { + iface_data->iface_get_config->ipv6s_arr = g_new(struct in6_addr, addr_count); + iface_data->iface_get_config->has_ipv6s = TRUE; + iface_data->iface_get_config->ipv6s_len = addr_count; } return; @@ -427,21 +484,31 @@ _get_config_iface_cb(GObject *source, GAsyncResult *result, gpointer user_data) iface_data->intern_iface_idx, iface_data->iface_get_config->hwaddr); - nm_sprintf_buf(buf, "%" G_GSSIZE_FORMAT "/ipv4/ipAddress/", iface_data->intern_iface_idx); + for (int IS_IPv4 = 1; IS_IPv4 >= 0; IS_IPv4--) { + int protocol = IS_IPv4 ? 4 : 6; + AzureIfaceReqData *cb_data = g_new(AzureIfaceReqData, 1); - nm_http_client_poll_req(NM_HTTP_CLIENT(source), - (uri = _azure_uri_interfaces(buf)), - HTTP_TIMEOUT_MS, - 512 * 1024, - 10000, - 1000, - NM_MAKE_STRV(NM_AZURE_METADATA_HEADER), - NULL, - get_config_data->intern_cancellable, - NULL, - NULL, - _get_config_ips_prefix_list_cb, - iface_data); + *cb_data = ((AzureIfaceReqData) {.data = iface_data, .protocol = protocol}); + + nm_sprintf_buf(buf, + "%" G_GSSIZE_FORMAT "/ipv%d/ipAddress/", + iface_data->intern_iface_idx, + protocol); + + nm_http_client_poll_req(NM_HTTP_CLIENT(source), + (uri = _azure_uri_interfaces(buf)), + HTTP_TIMEOUT_MS, + 512 * 1024, + 10000, + 1000, + NM_MAKE_STRV(NM_AZURE_METADATA_HEADER), + NULL, + get_config_data->intern_cancellable, + accept_not_found, + NULL, + _get_config_ips_prefix_list_cb, + cb_data); + } return; out_done: diff --git a/src/tests/client/test-client.py b/src/tests/client/test-client.py index a534d2710d..6b43f93a9d 100755 --- a/src/tests/client/test-client.py +++ b/src/tests/client/test-client.py @@ -2604,11 +2604,16 @@ class TestNmCloudSetup(unittest.TestCase): _azure_iface + "1/macAddress" + _azure_query, TestNmCloudSetup._mac2 ) self._mock_path(_azure_iface + "0/ipv4/ipAddress/" + _azure_query, "0\n") + self._mock_path(_azure_iface + "0/ipv6/ipAddress/" + _azure_query, "0\n") self._mock_path(_azure_iface + "1/ipv4/ipAddress/" + _azure_query, "0\n") self._mock_path( _azure_iface + "0/ipv4/ipAddress/0/privateIpAddress" + _azure_query, TestNmCloudSetup._ip1, ) + self._mock_path( + _azure_iface + "0/ipv6/ipAddress/0/privateIpAddress" + _azure_query, + TestNmCloudSetup._ip6_2, + ) self._mock_path( _azure_iface + "1/ipv4/ipAddress/0/privateIpAddress" + _azure_query, TestNmCloudSetup._ip2, @@ -2646,10 +2651,11 @@ class TestNmCloudSetup(unittest.TestCase): pexp.expect( r"interface\[0]: (received subnet address|received subnet prefix 20)" ) + # pexp.expect(r"interface\[1]: received ipv6 address " + TestNmCloudSetup._ip6_2) pexp.expect("get-config: success") pexp.expect("meta data received") - # One of the devices has no IPv4 configuration to be modified - pexp.expect("skip applied connection due to missing IPv4 configuration") + # One of the devices has no IP configuration to be modified + pexp.expect("skip applied connection due to missing IP configuration") # The other one was lacking an address set it up. pexp.expect("some changes were applied for provider azure") (exitstatus, signalstatus, valgrind_log) = self.ctx.cmd_close_pexpect(pexp)