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 <charles.my...@spirent.com>
---
 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 osv-dev+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to