Commit 42999c90 authored by Shradha Gupta's avatar Shradha Gupta Committed by Wei Liu

hv/hv_kvp_daemon:Support for keyfile based connection profile

Ifcfg config file support in NetworkManger is deprecated. This patch
provides support for the new keyfile config format for connection
profiles in NetworkManager. The patch modifies the hv_kvp_daemon code
to generate the new network configuration in keyfile
format(.ini-style format) along with a ifcfg format configuration.
The ifcfg format configuration is also retained to support easy
backward compatibility for distro vendors. These configurations are
stored in temp files which are further translated using the
hv_set_ifconfig.sh script. This script is implemented by individual
distros based on the network management commands supported.
For example, RHEL's implementation could be found here:
https://gitlab.com/redhat/centos-stream/src/hyperv-daemons/-/blob/c9s/hv_set_ifconfig.sh
Debian's implementation could be found here:
https://github.com/endlessm/linux/blob/master/debian/cloud-tools/hv_set_ifconfig

The next part of this support is to let the Distro vendors consume
these modified implementations to the new configuration format.

Tested-on: Rhel9(Hyper-V, Azure)(nm and ifcfg files verified)
Signed-off-by: default avatarShradha Gupta <shradhagupta@linux.microsoft.com>
Reviewed-by: default avatarSaurabh Sengar <ssengar@linux.microsoft.com>
Reviewed-by: default avatarAni Sinha <anisinha@redhat.com>
Signed-off-by: default avatarWei Liu <wei.liu@kernel.org>
Link: https://lore.kernel.org/r/1696847920-31125-1-git-send-email-shradhagupta@linux.microsoft.com
parent cfc7461a
...@@ -1171,12 +1171,79 @@ static int process_ip_string(FILE *f, char *ip_string, int type) ...@@ -1171,12 +1171,79 @@ static int process_ip_string(FILE *f, char *ip_string, int type)
return 0; return 0;
} }
/*
* Only IPv4 subnet strings needs to be converted to plen
* For IPv6 the subnet is already privided in plen format
*/
static int kvp_subnet_to_plen(char *subnet_addr_str)
{
int plen = 0;
struct in_addr subnet_addr4;
/*
* Convert subnet address to binary representation
*/
if (inet_pton(AF_INET, subnet_addr_str, &subnet_addr4) == 1) {
uint32_t subnet_mask = ntohl(subnet_addr4.s_addr);
while (subnet_mask & 0x80000000) {
plen++;
subnet_mask <<= 1;
}
} else {
return -1;
}
return plen;
}
static int process_ip_string_nm(FILE *f, char *ip_string, char *subnet,
int is_ipv6)
{
char addr[INET6_ADDRSTRLEN];
char subnet_addr[INET6_ADDRSTRLEN];
int error, i = 0;
int ip_offset = 0, subnet_offset = 0;
int plen;
memset(addr, 0, sizeof(addr));
memset(subnet_addr, 0, sizeof(subnet_addr));
while (parse_ip_val_buffer(ip_string, &ip_offset, addr,
(MAX_IP_ADDR_SIZE * 2)) &&
parse_ip_val_buffer(subnet,
&subnet_offset,
subnet_addr,
(MAX_IP_ADDR_SIZE *
2))) {
if (!is_ipv6)
plen = kvp_subnet_to_plen((char *)subnet_addr);
else
plen = atoi(subnet_addr);
if (plen < 0)
return plen;
error = fprintf(f, "address%d=%s/%d\n", ++i, (char *)addr,
plen);
if (error < 0)
return error;
memset(addr, 0, sizeof(addr));
memset(subnet_addr, 0, sizeof(subnet_addr));
}
return 0;
}
static int kvp_set_ip_info(char *if_name, struct hv_kvp_ipaddr_value *new_val) static int kvp_set_ip_info(char *if_name, struct hv_kvp_ipaddr_value *new_val)
{ {
int error = 0; int error = 0;
char if_file[PATH_MAX]; char if_filename[PATH_MAX];
FILE *file; char nm_filename[PATH_MAX];
FILE *ifcfg_file, *nmfile;
char cmd[PATH_MAX]; char cmd[PATH_MAX];
int is_ipv6 = 0;
char *mac_addr; char *mac_addr;
int str_len; int str_len;
...@@ -1197,7 +1264,7 @@ static int kvp_set_ip_info(char *if_name, struct hv_kvp_ipaddr_value *new_val) ...@@ -1197,7 +1264,7 @@ static int kvp_set_ip_info(char *if_name, struct hv_kvp_ipaddr_value *new_val)
* in a given distro to configure the interface and so are free * in a given distro to configure the interface and so are free
* ignore information that may not be relevant. * ignore information that may not be relevant.
* *
* Here is the format of the ip configuration file: * Here is the ifcfg format of the ip configuration file:
* *
* HWADDR=macaddr * HWADDR=macaddr
* DEVICE=interface name * DEVICE=interface name
...@@ -1220,6 +1287,32 @@ static int kvp_set_ip_info(char *if_name, struct hv_kvp_ipaddr_value *new_val) ...@@ -1220,6 +1287,32 @@ static int kvp_set_ip_info(char *if_name, struct hv_kvp_ipaddr_value *new_val)
* tagged as IPV6_DEFAULTGW and IPV6 NETMASK will be tagged as * tagged as IPV6_DEFAULTGW and IPV6 NETMASK will be tagged as
* IPV6NETMASK. * IPV6NETMASK.
* *
* Here is the keyfile format of the ip configuration file:
*
* [ethernet]
* mac-address=macaddr
* [connection]
* interface-name=interface name
*
* [ipv4]
* method=<protocol> (where <protocol> is "auto" if DHCP is configured
* or "manual" if no boot-time protocol should be used)
*
* address1=ipaddr1/plen
* address2=ipaddr2/plen
*
* gateway=gateway1;gateway2
*
* dns=dns1;dns2
*
* [ipv6]
* address1=ipaddr1/plen
* address2=ipaddr2/plen
*
* gateway=gateway1;gateway2
*
* dns=dns1;dns2
*
* The host can specify multiple ipv4 and ipv6 addresses to be * The host can specify multiple ipv4 and ipv6 addresses to be
* configured for the interface. Furthermore, the configuration * configured for the interface. Furthermore, the configuration
* needs to be persistent. A subsequent GET call on the interface * needs to be persistent. A subsequent GET call on the interface
...@@ -1227,14 +1320,29 @@ static int kvp_set_ip_info(char *if_name, struct hv_kvp_ipaddr_value *new_val) ...@@ -1227,14 +1320,29 @@ static int kvp_set_ip_info(char *if_name, struct hv_kvp_ipaddr_value *new_val)
* call. * call.
*/ */
snprintf(if_file, sizeof(if_file), "%s%s%s", KVP_CONFIG_LOC, /*
"/ifcfg-", if_name); * We are populating both ifcfg and nmconnection files
*/
snprintf(if_filename, sizeof(if_filename), "%s%s%s", KVP_CONFIG_LOC,
"/ifcfg-", if_name);
file = fopen(if_file, "w"); ifcfg_file = fopen(if_filename, "w");
if (file == NULL) { if (!ifcfg_file) {
syslog(LOG_ERR, "Failed to open config file; error: %d %s", syslog(LOG_ERR, "Failed to open config file; error: %d %s",
errno, strerror(errno)); errno, strerror(errno));
return HV_E_FAIL;
}
snprintf(nm_filename, sizeof(nm_filename), "%s%s%s%s", KVP_CONFIG_LOC,
"/", if_name, ".nmconnection");
nmfile = fopen(nm_filename, "w");
if (!nmfile) {
syslog(LOG_ERR, "Failed to open config file; error: %d %s",
errno, strerror(errno));
fclose(ifcfg_file);
return HV_E_FAIL; return HV_E_FAIL;
} }
...@@ -1248,14 +1356,31 @@ static int kvp_set_ip_info(char *if_name, struct hv_kvp_ipaddr_value *new_val) ...@@ -1248,14 +1356,31 @@ static int kvp_set_ip_info(char *if_name, struct hv_kvp_ipaddr_value *new_val)
goto setval_error; goto setval_error;
} }
error = kvp_write_file(file, "HWADDR", "", mac_addr); error = kvp_write_file(ifcfg_file, "HWADDR", "", mac_addr);
free(mac_addr); if (error < 0)
goto setmac_error;
error = kvp_write_file(ifcfg_file, "DEVICE", "", if_name);
if (error < 0)
goto setmac_error;
error = fprintf(nmfile, "\n[connection]\n");
if (error < 0)
goto setmac_error;
error = kvp_write_file(nmfile, "interface-name", "", if_name);
if (error) if (error)
goto setval_error; goto setmac_error;
error = kvp_write_file(file, "DEVICE", "", if_name); error = fprintf(nmfile, "\n[ethernet]\n");
if (error < 0)
goto setmac_error;
error = kvp_write_file(nmfile, "mac-address", "", mac_addr);
if (error) if (error)
goto setval_error; goto setmac_error;
free(mac_addr);
/* /*
* The dhcp_enabled flag is only for IPv4. In the case the host only * The dhcp_enabled flag is only for IPv4. In the case the host only
...@@ -1263,47 +1388,91 @@ static int kvp_set_ip_info(char *if_name, struct hv_kvp_ipaddr_value *new_val) ...@@ -1263,47 +1388,91 @@ static int kvp_set_ip_info(char *if_name, struct hv_kvp_ipaddr_value *new_val)
* proceed to parse and pass the IPv6 information to the * proceed to parse and pass the IPv6 information to the
* disto-specific script hv_set_ifconfig. * disto-specific script hv_set_ifconfig.
*/ */
/*
* First populate the ifcfg file format
*/
if (new_val->dhcp_enabled) { if (new_val->dhcp_enabled) {
error = kvp_write_file(file, "BOOTPROTO", "", "dhcp"); error = kvp_write_file(ifcfg_file, "BOOTPROTO", "", "dhcp");
if (error) if (error)
goto setval_error; goto setval_error;
} else { } else {
error = kvp_write_file(file, "BOOTPROTO", "", "none"); error = kvp_write_file(ifcfg_file, "BOOTPROTO", "", "none");
if (error) if (error)
goto setval_error; goto setval_error;
} }
/* error = process_ip_string(ifcfg_file, (char *)new_val->ip_addr,
* Write the configuration for ipaddress, netmask, gateway and IPADDR);
* name servers.
*/
error = process_ip_string(file, (char *)new_val->ip_addr, IPADDR);
if (error) if (error)
goto setval_error; goto setval_error;
error = process_ip_string(file, (char *)new_val->sub_net, NETMASK); error = process_ip_string(ifcfg_file, (char *)new_val->sub_net,
NETMASK);
if (error) if (error)
goto setval_error; goto setval_error;
error = process_ip_string(file, (char *)new_val->gate_way, GATEWAY); error = process_ip_string(ifcfg_file, (char *)new_val->gate_way,
GATEWAY);
if (error) if (error)
goto setval_error; goto setval_error;
error = process_ip_string(file, (char *)new_val->dns_addr, DNS); error = process_ip_string(ifcfg_file, (char *)new_val->dns_addr, DNS);
if (error) if (error)
goto setval_error; goto setval_error;
fclose(file); if (new_val->addr_family == ADDR_FAMILY_IPV6) {
error = fprintf(nmfile, "\n[ipv6]\n");
if (error < 0)
goto setval_error;
is_ipv6 = 1;
} else {
error = fprintf(nmfile, "\n[ipv4]\n");
if (error < 0)
goto setval_error;
}
/*
* Now we populate the keyfile format
*/
if (new_val->dhcp_enabled) {
error = kvp_write_file(nmfile, "method", "", "auto");
if (error < 0)
goto setval_error;
} else {
error = kvp_write_file(nmfile, "method", "", "manual");
if (error < 0)
goto setval_error;
}
/*
* Write the configuration for ipaddress, netmask, gateway and
* name services
*/
error = process_ip_string_nm(nmfile, (char *)new_val->ip_addr,
(char *)new_val->sub_net, is_ipv6);
if (error < 0)
goto setval_error;
error = fprintf(nmfile, "gateway=%s\n", (char *)new_val->gate_way);
if (error < 0)
goto setval_error;
error = fprintf(nmfile, "dns=%s\n", (char *)new_val->dns_addr);
if (error < 0)
goto setval_error;
fclose(nmfile);
fclose(ifcfg_file);
/* /*
* Now that we have populated the configuration file, * Now that we have populated the configuration file,
* invoke the external script to do its magic. * invoke the external script to do its magic.
*/ */
str_len = snprintf(cmd, sizeof(cmd), KVP_SCRIPTS_PATH "%s %s", str_len = snprintf(cmd, sizeof(cmd), KVP_SCRIPTS_PATH "%s %s %s",
"hv_set_ifconfig", if_file); "hv_set_ifconfig", if_filename, nm_filename);
/* /*
* This is a little overcautious, but it's necessary to suppress some * This is a little overcautious, but it's necessary to suppress some
* false warnings from gcc 8.0.1. * false warnings from gcc 8.0.1.
...@@ -1316,14 +1485,16 @@ static int kvp_set_ip_info(char *if_name, struct hv_kvp_ipaddr_value *new_val) ...@@ -1316,14 +1485,16 @@ static int kvp_set_ip_info(char *if_name, struct hv_kvp_ipaddr_value *new_val)
if (system(cmd)) { if (system(cmd)) {
syslog(LOG_ERR, "Failed to execute cmd '%s'; error: %d %s", syslog(LOG_ERR, "Failed to execute cmd '%s'; error: %d %s",
cmd, errno, strerror(errno)); cmd, errno, strerror(errno));
return HV_E_FAIL; return HV_E_FAIL;
} }
return 0; return 0;
setmac_error:
free(mac_addr);
setval_error: setval_error:
syslog(LOG_ERR, "Failed to write config file"); syslog(LOG_ERR, "Failed to write config file");
fclose(file); fclose(ifcfg_file);
fclose(nmfile);
return error; return error;
} }
......
...@@ -18,12 +18,12 @@ ...@@ -18,12 +18,12 @@
# #
# This example script is based on a RHEL environment. # This example script is based on a RHEL environment.
# #
# Here is the format of the ip configuration file: # Here is the ifcfg format of the ip configuration file:
# #
# HWADDR=macaddr # HWADDR=macaddr
# DEVICE=interface name # DEVICE=interface name
# BOOTPROTO=<protocol> (where <protocol> is "dhcp" if DHCP is configured # BOOTPROTO=<protocol> (where <protocol> is "dhcp" if DHCP is configured
# or "none" if no boot-time protocol should be used) # or "none" if no boot-time protocol should be used)
# #
# IPADDR0=ipaddr1 # IPADDR0=ipaddr1
# IPADDR1=ipaddr2 # IPADDR1=ipaddr2
...@@ -41,6 +41,32 @@ ...@@ -41,6 +41,32 @@
# tagged as IPV6_DEFAULTGW and IPV6 NETMASK will be tagged as # tagged as IPV6_DEFAULTGW and IPV6 NETMASK will be tagged as
# IPV6NETMASK. # IPV6NETMASK.
# #
# Here is the keyfile format of the ip configuration file:
#
# [ethernet]
# mac-address=macaddr
# [connection]
# interface-name=interface name
#
# [ipv4]
# method=<protocol> (where <protocol> is "auto" if DHCP is configured
# or "manual" if no boot-time protocol should be used)
#
# address1=ipaddr1/plen
# address=ipaddr2/plen
#
# gateway=gateway1;gateway2
#
# dns=dns1;
#
# [ipv6]
# address1=ipaddr1/plen
# address2=ipaddr1/plen
#
# gateway=gateway1;gateway2
#
# dns=dns1;dns2
#
# The host can specify multiple ipv4 and ipv6 addresses to be # The host can specify multiple ipv4 and ipv6 addresses to be
# configured for the interface. Furthermore, the configuration # configured for the interface. Furthermore, the configuration
# needs to be persistent. A subsequent GET call on the interface # needs to be persistent. A subsequent GET call on the interface
...@@ -48,18 +74,19 @@ ...@@ -48,18 +74,19 @@
# call. # call.
# #
echo "IPV6INIT=yes" >> $1 echo "IPV6INIT=yes" >> $1
echo "NM_CONTROLLED=no" >> $1 echo "NM_CONTROLLED=no" >> $1
echo "PEERDNS=yes" >> $1 echo "PEERDNS=yes" >> $1
echo "ONBOOT=yes" >> $1 echo "ONBOOT=yes" >> $1
cp $1 /etc/sysconfig/network-scripts/ cp $1 /etc/sysconfig/network-scripts/
chmod 600 $2
interface=$(echo $2 | awk -F - '{ print $2 }')
filename="${2##*/}"
sed '/\[connection\]/a autoconnect=true' $2 > /etc/NetworkManager/system-connections/${filename}
interface=$(echo $1 | awk -F - '{ print $2 }')
/sbin/ifdown $interface 2>/dev/null /sbin/ifdown $interface 2>/dev/null
/sbin/ifup $interface 2>/dev/null /sbin/ifup $interface 2>/dev/null
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment