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.
>
> 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].
> For more options, visit https://groups.google.com/d/optout.
>

-- 
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].
For more options, visit https://groups.google.com/d/optout.

Reply via email to