I don't see that this was applied? The reason I ask is that we're using cloud-init to set the IP addresses on multiple interfaces inside OSv (to separate database traffic from the protocols). I have a secondary patch to turn off dhcp that we also use.
I have my own network-module.cc, as I missed this one on the list. I've just fixed up the cloud-init code in my tree to deal with the libboost_program_options fall out and *then* I discover this code. So how to proceed - guidance please? Charles' code is more complete than mine, so I could try and merge that and get it working on the current codebase - would that be acceptable? Rick On Mon, 2018-08-13 at 12:32 +0300, Nadav Har'El wrote: > All looks good. Thanks. > > > -- > Nadav Har'El > [email protected] > > On Tue, Aug 7, 2018 at 5:49 AM, Charles Myers < > [email protected]> wrote: > > https://cloudinit.readthedocs.io/en/latest/topics/network-config-format-v1.html > > > > Currently only interface naming, static IP, routes and DNS are > > supported.e > > > > Signed-off-by: Charles Myers <[email protected]> > > --- > > modules/cloud-init/Makefile | 2 +- > > modules/cloud-init/main.cc | 77 +++++--- > > modules/cloud-init/network-module.cc | 369 > > +++++++++++++++++++++++++++++++++++ > > modules/cloud-init/network-module.hh | 43 ++++ > > 4 files changed, 465 insertions(+), 26 deletions(-) > > create mode 100644 modules/cloud-init/network-module.cc > > create mode 100644 modules/cloud-init/network-module.hh > > > > diff --git a/modules/cloud-init/Makefile b/modules/cloud- > > init/Makefile > > index daee8c3..ae45b24 100644 > > --- a/modules/cloud-init/Makefile > > +++ b/modules/cloud-init/Makefile > > @@ -13,7 +13,7 @@ INCLUDES += -I$(HTTPSERVER_API_DIR) > > > > # the build target executable: > > TARGET = cloud-init > > -CPP_FILES := client.cc cloud-init.cc data-source.cc main.cc > > template.cc cassandra-module.cc json.cc > > +CPP_FILES := client.cc cloud-init.cc data-source.cc main.cc > > template.cc network-module.cc cassandra-module.cc json.cc > > OBJ_FILES := $(addprefix obj/,$(CPP_FILES:.cc=.o)) > > DEPS := $(OBJ_FILES:.o=.d) > > > > diff --git a/modules/cloud-init/main.cc b/modules/cloud- > > init/main.cc > > index 5af23f0..a6674ef 100644 > > --- a/modules/cloud-init/main.cc > > +++ b/modules/cloud-init/main.cc > > @@ -9,6 +9,7 @@ > > #include <sys/stat.h> > > #include <unistd.h> > > #include "cloud-init.hh" > > +#include "network-module.hh" > > #include "files-module.hh" > > #include "server-module.hh" > > #include "cassandra-module.hh" > > @@ -24,59 +25,84 @@ using namespace std; > > using namespace init; > > namespace po = boost::program_options; > > > > -// config_disk() allows to use NoCloud VM configuration method - > > see > > +// config_disk() allows to use NoCloud and ConfigDrive VM > > configuration method - see > > // > > http://cloudinit.readthedocs.io/en/0.7.9/topics/datasources/nocloud.html > > . > > +// > > http://cloudinit.readthedocs.io/en/0.7.9/topics/datasources/configdrive.html > > +// > > // NoCloud method provides two files with cnfiguration data > > (/user-data and > > // /meta-data) on a disk. The disk is required to have label > > "cidata". > > // It can contain ISO9660 or FAT filesystem. > > // > > +// ConfigDrive (version 2) method uses an unpartitioned VFAT or > > ISO9660 disk > > +// with files. > > +// openstack/ > > +// - 2012-08-10/ or latest/ > > +// - meta_data.json > > +// - user_data (not mandatory) > > +// - content/ > > +// - 0000 (referenced content files) > > +// - 0001 > > +// - .... > > +// ec2 > > +// - latest/ > > +// - meta-data.json (not mandatory) > > +// - user-data > > +// > > // config_disk() checks whether we have a second disk (/dev/vblkX) > > with > > // ISO image, and if there is, it copies the configuration file > > from > > -// /user-data to the given file. > > +// the user user-data file to the given file. > > // config_disk() returns true if it has successfully read the > > configuration > > -// into the requested file. It triest to get configuratioe from > > first few > > +// into the requested file. It tries to get configuration from > > first few > > // vblk devices, namely vblk1 to vblk10. > > // > > // OSv implementation limitations: > > // The /meta-data file is currently ignored. > > // Only ISO9660 filesystem is supported. > > -// The mandatory "cidata" volume label is not checked. > > +// The mandatory "cidata" (NoCloud) and "config-2" (ConfigDrive) > > volume labels are not checked. > > // > > // Example ISO image can be created by running > > // cloud-localds cloud-init.img cloud-init.yaml > > // The cloud-localds command is provided by cloud-utils package > > (fedora). > > static bool config_disk(const char* outfile) { > > + const char * userdata_file_paths[] { > > + "/user-data", // NoCloud > > + "/openstack/latest/user_data", // ConfigDrive OpenStack > > + "/ec2/latest/user-data", // ConfigDrive EC2 > > + }; > > + > > for (int ii=1; ii<=10; ii++) { > > char disk[20]; > > - char srcfile[] = "/user-data"; > > struct stat sb; > > - int ret; > > - int app_ret = -1; > > > > snprintf(disk, sizeof(disk), "/dev/vblk%d", ii); > > - ret = stat(disk, &sb); > > - if (ret != 0) { > > - continue; > > - } > > > > - std::vector<std::string> cmd = {"/usr/bin/iso-read.so", "- > > e", srcfile, "-o", outfile, disk}; > > - osv::run(cmd[0], cmd, &app_ret); > > - if (app_ret != 0) { > > - debug("cloud-init: warning, %s exited with code %d (%s > > is not ISO image?)\n", cmd[0], app_ret, disk); > > + if (stat(disk, &sb) != 0) { > > continue; > > } > > - ret = stat(outfile, &sb); > > - if (ret != 0) { > > - debug("cloud-init: disk %s, stat(%s) failed, > > errno=%d\n", disk, outfile, errno); > > - continue; > > - } > > - if ((sb.st_mode & S_IFMT) != S_IFREG) { > > - debug("cloud-init: disk %s, %s is not a file\n", disk, > > outfile); > > - return false; > > + > > + debug("cloud-init: checking disk %s\n", disk); > > + > > + for (auto & srcfile : userdata_file_paths) { > > + int app_ret = -1; > > + std::vector<std::string> cmd = {"/usr/bin/iso- > > read.so", "-e", srcfile , "-o", outfile, disk}; > > + osv::run(cmd[0], cmd, &app_ret); > > + if (app_ret != 0) { > > + debug("cloud-init: warning, %s exited with code %d > > (%s is not ISO image?)\n", cmd[0], app_ret, disk); > > + continue; > > + } > > + if (stat(outfile, &sb) != 0) { > > + debug("cloud-init: stat(%s) failed, errno=%d\n", > > outfile, errno); > > + continue; > > + } > > + if ((sb.st_mode & S_IFMT) != S_IFREG) { > > + debug("cloud-init: %s is not a file\n", outfile); > > + continue; > > + } > > + debug("cloud-init: copied file %s -> %s from ISO image > > %s\n", srcfile, outfile, disk); > > + return true; > > } > > - debug("cloud-init: copied file %s -> %s from ISO image > > %s\n", srcfile, outfile, disk); > > - return true; > > } > > + > > return false; > > } > > > > @@ -106,6 +132,7 @@ int main(int argc, char* argv[]) > > osvinit init(config.count("skip-error") > 0, > > config.count("force-probe") > 0); > > auto scripts = make_shared<script_module>(); > > init.add_module(scripts); > > + init.add_module(make_shared<network_module>()); > > init.add_module(make_shared<mount_module>()); > > init.add_module(make_shared<hostname_module>()); > > init.add_module(make_shared<files_module>()); > > diff --git a/modules/cloud-init/network-module.cc b/modules/cloud- > > init/network-module.cc > > new file mode 100644 > > index 0000000..cf73e52 > > --- /dev/null > > +++ b/modules/cloud-init/network-module.cc > > @@ -0,0 +1,369 @@ > > +/* > > + * Copyright (C) 2014 Cloudius Systems, Ltd. > > + * > > + * This work is open source software, licensed under the terms of > > the > > + * BSD license as described in the LICENSE file in the top-level > > directory. > > + */ > > + > > +#include <bsd/porting/networking.hh> > > +#include <bsd/porting/route.h> > > +#include <osv/debug.hh> > > + > > +#include "network-module.hh" > > +#include <boost/asio/ip/address.hpp> > > +#include <boost/algorithm/string.hpp> > > + > > +#include <ifaddrs.h> > > +#include <api/netpacket/packet.h> > > + > > +#include "libc/network/__dns.hh" > > + > > +static int mac_str_to_addr(const std::string &str, uint8_t *addr) > > +{ > > + int values[6]; > > + > > + if (sscanf(str.c_str(), "%x:%x:%x:%x:%x:%x", > > + &values[0], &values[1], &values[2], > > + &values[3], &values[4], &values[5]) != 6) { > > + return -1; > > + } > > + > > + for (int i=0; i<6; ++i) { > > + addr[i] = values[i]; > > + } > > + return 0; > > +} > > + > > +int if_rename(const std::string &ifname, const std::string > > &new_ifname) > > +{ > > + struct ifreq req; > > + int sock; > > + int result = 0; > > + > > + memset(&req, 0, sizeof(req)); > > + strncpy(req.ifr_name, ifname.c_str(), sizeof(req.ifr_name)-1); > > + strncpy(req.ifr_newname, new_ifname.c_str(), > > sizeof(req.ifr_newname)-1); > > + > > + sock = socket(AF_INET, SOCK_DGRAM, 0); > > + if (sock < 0) { > > + debug("cloud-init: %s error. socket() failed. %s\n", > > __FUNCTION__, strerror(errno)); > > + return -1; > > + } > > + > > + if (ioctl(sock, SIOCSIFNAME, &req) < 0) { > > + debug("cloud-init: %s error. ioctl() SIOSIFNAME failed. > > %s\n", __FUNCTION__, strerror(errno)); > > + result = -1; > > + } > > + close(sock); > > + return result; > > +} > > + > > +int if_set_mac(const std::string &ifname, const std::string > > &mac_addr) > > +{ > > + struct ifreq req; > > + int sock; > > + int result = 0; > > + > > + memset(&req, 0, sizeof(req)); > > + strncpy(req.ifr_name, ifname.c_str(), sizeof(req.ifr_name)-1); > > + > > + if (mac_str_to_addr(mac_addr, (uint8_t* > > )req.ifr_hwaddr.sa_data) < 0) { > > + debug("cloud-init: %s error. Invalid MAC %s\n", > > __FUNCTION__, mac_addr.c_str()); > > + return -1; > > + } > > + > > + sock = socket(AF_INET, SOCK_DGRAM, 0); > > + if (sock < 0) { > > + debug("cloud-init: %s error. socket() failed. %s\n", > > __FUNCTION__, strerror(errno)); > > + return -1; > > + } > > + if (ioctl(sock, SIOCSIFHWADDR, &req) < 0) { > > + debug("cloud-init: %s error. ioctl() SIOCSIFHWADDR > > failed. %s\n", __FUNCTION__, strerror(errno)); > > + result = -1; > > + } > > + close(sock); > > + return result; > > +} > > + > > +int if_find_name_by_mac(const std::string &mac_addr, std::string > > &ifname) > > +{ > > + struct ifaddrs *ifaddr = NULL; > > + struct ifaddrs *ifa = NULL; > > + uint8_t hwaddr[6]; > > + int result = -1; > > + > > + if (mac_str_to_addr(mac_addr, hwaddr) < 0) { > > + debug("cloud-init: %s error. Invalid MAC %s\n", > > __FUNCTION__, mac_addr.c_str()); > > + return -1; > > + } > > + > > + if (getifaddrs(&ifaddr) == -1) { > > + debug("cloud-init: %s error. getifaddrs() failed: %s\n", > > __FUNCTION__, strerror(errno)); > > + return -1; > > + } > > + > > + for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) { > > + if (!ifa->ifa_addr) continue; > > + if (ifa->ifa_addr->sa_family != AF_PACKET) continue; > > + struct sockaddr_ll *sa = (struct sockaddr_ll*)ifa- > > >ifa_addr; > > + if (sa->sll_halen != 6) > > + continue; > > + if (memcmp(hwaddr, sa->sll_addr, 6) != 0) > > + continue; > > + ifname = ifa->ifa_name; > > + result = 0; > > + break; > > + } > > + > > + freeifaddrs(ifaddr); > > + return result; > > +} > > + > > +void network_module::configure_interface(const YAML::Node& node, > > network_module::config_state& state) > > +{ > > + if (node["type"]) { > > + std::string type = node["type"].as<std::string>(); > > + if (type == "physical") { > > + configure_physical_interface(node, state); > > + } > > + } > > +} > > + > > +void network_module::configure_physical_interface(const > > YAML::Node& node, network_module::config_state& state) > > +{ > > + std::string if_name; > > + std::string mac_addr; > > + > > + if (node["name"]) { > > + if_name = node["name"].as<std::string>(); > > + if (state.configured_interfaces.find(if_name) != > > state.configured_interfaces.end()) { > > + debug("cloud-init: %s error. interface %s included in > > configuration multiple times\n", __FUNCTION__, if_name.c_str()); > > + return; > > + } > > + } > > + if (node["mac_address"]) { > > + // Find interface with MAC address > > + mac_addr = node["mac_address"].as<std::string>(); > > + std::string fname; > > + if_find_name_by_mac(mac_addr, fname); > > + if (fname != "") { > > + // Found interface matching the MAC address > > + if (if_name != "" && fname != if_name) { > > + // Different name specified so try to rename the > > interface > > + if (if_rename(fname, if_name) != 0) { > > + debug("cloud-init: %s error. Failed to rename > > interface %s to %s\n", __FUNCTION__, fname.c_str(), > > if_name.c_str()); > > + if_name = fname; > > + } else { > > + // Update the physical interface list > > + auto iter = > > std::find(state.physical_interfaces.begin(), > > state.physical_interfaces.end(), fname); > > + if (iter != state.physical_interfaces.end()) { > > + (*iter) = if_name; > > + } > > + } > > + } else { > > + // No name specified so use existing interface > > name > > + if_name = fname; > > + } > > + } else { > > + // No interface matching the MAC address > > + if (if_name == "") { > > + debug("cloud-init: %s error. Failed to find > > interface with MAC address %s\n", __FUNCTION__, mac_addr.c_str()); > > + return; > > + } > > +#if 0 // Setting MAC is not supported > > + // Set interface MAC > > + if_set_mac(if_name, mac_addr); > > +#endif > > + } > > + } > > + if (if_name == "") { > > + // Unable to find matching interface > > + for (auto &tmp_if : state.physical_interfaces) { > > + if (state.configured_interfaces.find(tmp_if) != > > state.configured_interfaces.end()) > > + continue; > > + if_name = tmp_if; > > + break; > > + } > > + if (if_name == "") { > > + debug("cloud-init: %s error. Interface name not > > specified.\n", __FUNCTION__); > > + return; > > + } > > + } else { > > + // Validate interface name is okay to use > > + if (std::find(state.physical_interfaces.begin(), > > state.physical_interfaces.end(), if_name) == > > state.physical_interfaces.end()) { > > + debug("cloud-init: %s error. Interface %s not > > found\n", __FUNCTION__, if_name.c_str()); > > + return; > > + } > > + } > > + > > + state.configured_interfaces.insert(if_name); > > + > > + if (node["mtu"]) { > > + // Configure MTU > > + int err; > > + int mtu = node["mtu"].as<int>(); > > + if ((err = osv::if_set_mtu(if_name, mtu)) != 0){ > > + debug("cloud-init: %s errror. Failed to set %s mtu > > %d. err=%d\n", __FUNCTION__, if_name.c_str(), mtu, err); > > + } > > + } > > + if (node["subnets"]) { > > + for (auto& subnet : node["subnets"]) { > > + std::string subnet_type; > > + if (subnet["type"]) { > > + subnet_type = subnet["type"].as<std::string>(); > > + } else { > > + subnet_type = "static"; > > + } > > + if (subnet_type == "static" || > > + subnet_type == "static6") { > > + if (!subnet["address"]) { > > + continue; > > + } > > + // TODO: Disable DHCP per interface > > + // dhcp_release(); > > + std::string address = > > subnet["address"].as<std::string>(); > > + std::string netmask; > > + std::vector<std::string> addr_prefix; > > + bool ipv6 = false; > > + > > + boost::split(addr_prefix, address, > > boost::is_any_of("/"), boost::token_compress_on); > > + if (addr_prefix.size() == 2) { > > + address = addr_prefix[0]; > > + netmask = addr_prefix[1]; > > + } else { > > + if (subnet["netmask"]) { > > + netmask = > > subnet["netmask"].as<std::string>(); > > + } > > + } > > + > > + try { > > + boost::asio::ip::address addr; > > + ipv6 = > > boost::asio::ip::address::from_string(address).is_v6(); > > + } catch(std::exception const &ex) { > > + debug("cloud-init: %s error. Not a valid IP > > address %s\n", > > + __FUNCTION__, address.c_str()); > > + continue; > > + } > > + > > + // Add address to interface > > + if (osv::if_add_addr(if_name, address, netmask) != > > 0){ > > + debug("cloud-init: %s error. Failed adding > > address %s/%s to interface %s\n", > > + __FUNCTION__, > > + address.c_str(), netmask.c_str(), > > if_name.c_str()); > > + continue; > > + } > > + if (subnet["gateway"]) { > > + std::string gateway = > > node["gateway"].as<std::string>(); > > + std::string network = ipv6 ? "::" : "0.0.0.0"; > > + std::string netmask = ipv6 ? "::" : "0.0.0.0"; > > + > > + osv_route_add_network(network.c_str(), > > + netmask.c_str(), > > + gateway.c_str()); > > + } > > + > > + if (subnet["routes"]) { > > + for (auto& route : subnet["routes"]) { > > + if (route["gateway"]) { > > + std::string gateway = > > node["gateway"].as<std::string>(); > > + std::string network; > > + std::string netmask; > > + if (route["network"]) { > > + network = > > route["network"].as<std::string>(); > > + } else { > > + network = ipv6 ? "::" : "0.0.0.0"; > > + } > > + if (route["netmask"]) { > > + netmask = > > route["netmask"].as<std::string>(); > > + } else { > > + netmask = ipv6 ? "::" : "0.0.0.0"; > > + } > > + > > + osv_route_add_network(network.c_str(), > > + netmask.c_str(), > > + > > gateway.c_str()); > > + } > > + } > > + } > > + if (subnet["dns_nameservers"]) { > > + auto& dns_servers = > > subnet["dns_nameservers"].as<std::vector<std::string>>(); > > + > > state.dns_servers.insert(state.dns_servers.end(), > > + dns_servers.begin(), > > + dns_servers.end()); > > + } > > + } > > + else if (subnet_type == "dhcp") { > > + // TODO: Enable DHCP per interface > > + // dhcp_start(true) > > + } > > + else if (subnet_type == "nameserver") { > > + if (!subnet["address"]) > > + continue; > > + auto& dns_servers = > > subnet["address"].as<std::vector<std::string>>(); > > + state.dns_servers.insert(state.dns_servers.end(), > > + dns_servers.begin(), > > + dns_servers.end()); > > + } > > + } > > + } > > +} > > + > > +void network_module::handle(const YAML::Node& doc) > > +{ > > + if (doc["version"]) { > > + int version = doc["version"].as<int>(); > > + if (version != 1) { > > + debug("cloud-init: version %d is not supported\n", > > version); > > + return; > > + } > > + } > > + > > + if (doc["config"]) { > > + const YAML::Node &config = doc["config"]; > > + network_module::config_state state; > > + > > + init_config_state(state); > > + for (auto& ifc_node : config) { > > + configure_interface(ifc_node, state); > > + } > > + > > + // Configure DNS servers > > + if (!state.dns_servers.empty()) { > > + std::set<std::string> dns_server_set; > > + std::vector<boost::asio::ip::address> dns_servers; > > + for (auto t : state.dns_servers) { > > + if (dns_server_set.find(t) != > > dns_server_set.end()) > > + continue; // Skip duplicates > > + auto addr = > > boost::asio::ip::address::from_string(t); > > + dns_servers.push_back(addr); > > + dns_server_set.insert(t); > > + } > > + osv::set_dns_config(dns_servers, > > std::vector<std::string>()); > > + } > > + } > > +} > > + > > +void > > network_module::init_config_state(network_module::config_state& > > state) > > +{ > > + struct ifaddrs *ifaddr = NULL; > > + struct ifaddrs *ifa = NULL; > > + > > + state.physical_interfaces.clear(); > > + state.configured_interfaces.clear(); > > + state.dns_servers.clear(); > > + > > + if (getifaddrs(&ifaddr) == -1) { > > + debug("cloud-init: %s failed. getifaddrs() failed: %s\n", > > __FUNCTION__, strerror(errno)); > > + return; > > + } > > + > > + for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) { > > + if (!ifa->ifa_addr) continue; > > + if (ifa->ifa_addr->sa_family != AF_PACKET) continue; > > + if (ifa->ifa_flags & IFF_LOOPBACK) continue; > > + state.physical_interfaces.push_back(ifa->ifa_name); > > + } > > + > > + freeifaddrs(ifaddr); > > +} > > + > > diff --git a/modules/cloud-init/network-module.hh b/modules/cloud- > > init/network-module.hh > > new file mode 100644 > > index 0000000..175edf5 > > --- /dev/null > > +++ b/modules/cloud-init/network-module.hh > > @@ -0,0 +1,43 @@ > > +/* > > + * Copyright (C) 2014 Cloudius Systems, Ltd. > > + * > > + * This work is open source software, licensed under the terms of > > the > > + * BSD license as described in the LICENSE file in the top-level > > directory. > > + */ > > + > > +#ifndef CLOUDINIT_NETWORK_HH_ > > +#define CLOUDINIT_NETWORK_HH_ > > + > > +#include "cloud-init.hh" > > + > > +#include <fstream> > > +#include <vector> > > +#include <set> > > + > > +class network_module : public init::config_module > > +{ > > +public: > > + > > + virtual void handle(const YAML::Node& doc) override; > > + > > + virtual std::string get_label() > > + { > > + return "network"; > > + } > > + > > +private: > > + > > + class config_state > > + { > > + public: > > + std::set<std::string> configured_interfaces; > > + std::vector<std::string> physical_interfaces; > > + std::vector<std::string> dns_servers; > > + }; > > + static void init_config_state(config_state& state); > > + static void configure_interface(const YAML::Node& node, > > config_state& state); > > + static void configure_physical_interface(const YAML::Node& > > node, config_state& state); > > + > > +}; > > + > > +#endif > > -- > > 2.7.4 > > -- You received this message because you are subscribed to the Google Groups "OSv Development" group. To unsubscribe from this group and stop receiving emails from it, send an email to [email protected]. To view this discussion on the web visit https://groups.google.com/d/msgid/osv-dev/338f62e13ff2228946c08f782d92f7ed286c186b.camel%40rossfell.co.uk.
