lxcpp: fix network config serialization
[platform/core/security/vasum.git] / tests / unit_tests / lxcpp / ut-network.cpp
index f797a6c..1f55976 100644 (file)
 
 #include "config.hpp"
 #include "config/manager.hpp"
+#include "logger/logger.hpp"
 #include "lxcpp/network-config.hpp"
+#include "lxcpp/process.hpp"
+#include "utils/execute.hpp"
 #include "ut.hpp"
 
 #include <iostream>
-#include <sched.h>
 
 #include <net/if.h>
 
-namespace {
-
 using namespace lxcpp;
-using namespace config;
+
+namespace {
 
 struct Fixture {
     Fixture() {}
@@ -49,8 +50,71 @@ struct Fixture {
         } while (std::find(iflist.begin(), iflist.end(), name) != iflist.end());
         return name;
     }
+
+    static void sendCmd(int fd, const char *txt) {
+        if (::write(fd, txt, 2) != 2) {
+            throw std::runtime_error("pipe write error");
+        }
+    }
 };
 
+
+int child_exec(void *_fd)
+{
+    int *fd = (int *)_fd;
+    char cmdbuf[2];
+    char cmd = '-';
+
+    ::close(fd[1]);
+    try {
+        lxcpp::NetworkInterface("lo").up();
+        for(;;) {
+            // child: waiting for parent
+            if (::read(fd[0], cmdbuf, 2) != 2) {
+                break;
+            }
+
+            cmd = cmdbuf[0];
+
+            if (cmd == '0') {
+                break;
+            } else if (cmd == 'a') {
+                const char *argv[] = {
+                    "ip", "a",  NULL
+                };
+                if (!utils::executeAndWait("/sbin/ip", argv)) {
+                    throw std::runtime_error("ip addr failed");
+                }
+            } else if (cmd == 'r') {
+                const char *argv[] = {
+                    "ip", "route", "list",  NULL
+                };
+                if (!utils::executeAndWait("/sbin/ip", argv)) {
+                    throw std::runtime_error("ip route failed");
+                }
+            } else if (cmd == 's') {
+                const char *argv[] = {
+                    "bash", NULL
+                };
+                if (!utils::executeAndWait("/bin/bash", argv)) {
+                    throw std::runtime_error("bash failed");
+                }
+            } else if (cmd == 'c') {
+                LOGW("connecting ... to be done");
+            } else {
+                continue;
+            }
+        }
+
+        //cleanup
+        ::close(fd[0]);
+        _exit(EXIT_SUCCESS);
+
+    } catch(...) {
+        _exit(EXIT_FAILURE);
+    }
+}
+
 } // namespace
 
 /*
@@ -64,36 +128,19 @@ BOOST_FIXTURE_TEST_SUITE(LxcppNetworkSuite, Fixture)
 BOOST_AUTO_TEST_CASE(NetworkListInterfaces)
 {
     std::vector<std::string> iflist;
-    BOOST_CHECK_NO_THROW(iflist = NetworkInterface::getInterfaces(0));
-
-    for (const auto& i : iflist) {
-        NetworkInterface ni(i);
-        std::cout << i << ": ";
-        Attrs attrs;
-        std::vector<InetAddr> addrs;
-        BOOST_CHECK_NO_THROW(attrs = ni.getAttrs());
-        for (const Attr& a : attrs) {
-            std::cout << a.name << "=" << a.value;
-            if (a.name == AttrName::FLAGS) {
-                uint32_t f = stoul(a.value);
-                std::cout << "(";
-                std::cout << ((f & IFF_UP) !=0 ? "UP" : "DONW");
-                std::cout << ")";
-            }
-            std::cout << "; ";
-        }
-        std::cout << std::endl;
-
-        BOOST_CHECK_NO_THROW(addrs = ni.getInetAddressList());
-        for (const InetAddr& a : addrs) {
-            std::cout << "   " << toString(a) << std::endl;
-        }
+    BOOST_CHECK_NO_THROW(iflist=NetworkInterface::getInterfaces(0));
+    for (const auto& ifn : iflist) {
+        const Attrs& attrs = NetworkInterface(ifn).getAttrs();
+        BOOST_CHECK(attrs.size() > 0);
     }
+
 }
 
 BOOST_AUTO_TEST_CASE(NetworkConfigSerialization)
 {
     std::string tmpConfigFile = "/tmp/netconfig.conf";
+    ::unlink(tmpConfigFile.c_str());
+
     NetworkConfig cfg;
     BOOST_CHECK_NO_THROW(config::saveToJsonString(cfg));
 
@@ -101,16 +148,17 @@ BOOST_AUTO_TEST_CASE(NetworkConfigSerialization)
     cfg.addInterfaceConfig("host-veth1", "zone-eth1", InterfaceType::BRIDGE);
     cfg.addInterfaceConfig("host-veth2", "zone-eth2", InterfaceType::MACVLAN);
 
-    InetAddr addr(0, 24, "1.2.3.4");
-    cfg.addInetConfig("zone-eth0", addr);
+    cfg.addInetConfig("zone-eth0", InetAddr("1.2.3.4", 24));
 
     config::saveToJsonFile(tmpConfigFile, cfg);
 
     NetworkConfig cfg2;
-    config::loadFromJsonFile(tmpConfigFile, cfg2);
+    BOOST_CHECK_NO_THROW(config::loadFromJsonFile(tmpConfigFile, cfg2));
 
-    int ifnum = cfg.getInterfaces().size();
-    for (int i = 0; i < ifnum; ++i) {
+    int ifn1 = cfg.getInterfaces().size();
+    int ifn2 = cfg2.getInterfaces().size();
+    BOOST_CHECK_EQUAL(ifn1, ifn2);
+    for (int i = 0; i < ifn2; ++i) {
         const NetworkInterfaceConfig& ni1 = cfg.getInterface(i);
         const NetworkInterfaceConfig& ni2 = cfg2.getInterface(i);
 
@@ -120,30 +168,27 @@ BOOST_AUTO_TEST_CASE(NetworkConfigSerialization)
         BOOST_CHECK(ni1.getMode() == ni2.getMode());
     }
 }
+
 BOOST_AUTO_TEST_CASE(NetworkBridgeCreateDestroy)
 {
-    std::string name = getUniqueName("lolo");
+    std::string name = getUniqueName("test-br");
     NetworkInterface ni(name);
-    InetAddr myip(0, 32, "10.100.1.1");
+    InetAddr myip("10.100.1.1", 32);
 
-    BOOST_CHECK_NO_THROW(ni.create(InterfaceType::BRIDGE, ""));
-    BOOST_CHECK_NO_THROW(ni.addInetAddr(myip);)
+    BOOST_CHECK_NO_THROW(ni.create(InterfaceType::BRIDGE));
+    ni.setMACAddress("12:22:33:44:55:66");    // note bit0=0 within first byte !!!
+    BOOST_CHECK_NO_THROW(ni.addInetAddr(myip));
 
     std::vector<std::string> iflist = NetworkInterface::getInterfaces(0);
     BOOST_CHECK(std::find(iflist.begin(), iflist.end(), name) != iflist.end());
 
     std::vector<InetAddr> addrs = ni.getInetAddressList();
     BOOST_CHECK(std::find(addrs.begin(), addrs.end(), myip) != addrs.end());
-    for (const auto& i : iflist) {
-        std::cout << "  " << i;
-    }
-    std::cout << std::endl;
-    for (const InetAddr& a : addrs) {
-        std::cout << "   " << toString(a) << std::endl;
-    }
 
     BOOST_CHECK_NO_THROW(ni.delInetAddr(myip));
     BOOST_CHECK_NO_THROW(ni.destroy());
+    iflist = NetworkInterface::getInterfaces(0);
+    BOOST_CHECK(std::find(iflist.begin(), iflist.end(), ni.getName()) == iflist.end());
 }
 
 BOOST_AUTO_TEST_CASE(NetworkMacVLanCreateDestroy)
@@ -151,7 +196,9 @@ BOOST_AUTO_TEST_CASE(NetworkMacVLanCreateDestroy)
     std::string masterif;
     std::vector<std::string> iflist = NetworkInterface::getInterfaces(0);
     for (const auto& ifn : iflist) {
-        if (ifn == "lo") continue;
+        if (ifn == "lo") {
+            continue;
+        }
         NetworkInterface n(ifn);
         if (n.status() == NetStatus::UP) {
             masterif = ifn;
@@ -159,14 +206,164 @@ BOOST_AUTO_TEST_CASE(NetworkMacVLanCreateDestroy)
         }
     }
 
-    NetworkInterface ni(getUniqueName("lolo"));
-    std::cout << " creating MACVLAN on " << masterif << std::endl;
+    NetworkInterface ni(getUniqueName("test-vlan"));
+    // creating MACVLAN on masterif
     BOOST_CHECK_NO_THROW(ni.create(InterfaceType::MACVLAN, masterif, MacVLanMode::VEPA));
 
     iflist = NetworkInterface::getInterfaces(0);
     BOOST_CHECK(std::find(iflist.begin(), iflist.end(), ni.getName()) != iflist.end());
 
+    // destroy MACVLAN
     BOOST_CHECK_NO_THROW(ni.destroy());
+
+    iflist = NetworkInterface::getInterfaces(0);
+    BOOST_CHECK(std::find(iflist.begin(), iflist.end(), ni.getName()) == iflist.end());
+}
+
+BOOST_AUTO_TEST_CASE(NetworkListRoutes)
+{
+    unsigned mainLo = 0;
+    std::vector<Route> routes;
+    // tbl MAIN, all devs
+    BOOST_CHECK_NO_THROW(routes = NetworkInterface::getRoutes(0));
+    for (auto route : routes) {
+        if (route.ifname == "lo") {
+            ++mainLo;
+        }
+    }
+
+    // tbl LOCAL, all devs
+    BOOST_CHECK_NO_THROW(routes = NetworkInterface::getRoutes(0,RoutingTable::LOCAL));
+
+    // tbl DEFAULT, all devs
+    BOOST_CHECK_NO_THROW(routes = NetworkInterface::getRoutes(0,RoutingTable::DEFAULT));
+
+    NetworkInterface ni("lo");
+    // tbl MAIN, dev lo
+    BOOST_CHECK_NO_THROW(routes = ni.getRoutes());
+    BOOST_CHECK(routes.size() == mainLo);
+
+    // tbl LOCAL, dev lo
+    BOOST_CHECK_NO_THROW(routes = ni.getRoutes(RoutingTable::LOCAL));
+}
+
+BOOST_AUTO_TEST_CASE(NetworkAddDelRoute)
+{
+    std::vector<Route> routes;
+
+    Route route = {
+        InetAddr("10.100.1.0", 24),//dst - destination network
+        InetAddr("", 0),           //src - not specified (prefix=0)
+        0,                         // metric
+        "",                        // ifname (used only when read routes)
+        RoutingTable::UNSPEC       // table (used only when read rotes)
+    };
+
+    NetworkInterface ni("lo");
+
+    BOOST_CHECK_NO_THROW(ni.addRoute(route));
+    BOOST_CHECK_NO_THROW(routes = ni.getRoutes());
+    BOOST_CHECK(std::find_if(routes.begin(), routes.end(),
+                            [&route](const Route& item) -> bool {
+                                return item.dst == route.dst;
+                            }
+                         ) != routes.end()
+    );
+
+    BOOST_CHECK_NO_THROW(ni.delRoute(route));
+    BOOST_CHECK_NO_THROW(routes = ni.getRoutes());
+    BOOST_CHECK(std::find_if(routes.begin(), routes.end(),
+                            [&route](const Route& item) -> bool {
+                                return item.dst == route.dst;
+                            }
+                         ) == routes.end()
+    );
+}
+
+BOOST_AUTO_TEST_CASE(NetworkNamespaceCreate)
+{
+    int fd[2];
+    int r = ::pipe(fd);
+    BOOST_CHECK(r != -1);
+
+    pid_t pid = lxcpp::clone(child_exec, fd, CLONE_NEWNET);
+    ::close(fd[0]);
+
+    //directives for child process
+    sendCmd(fd[1], "0"); // exit
+
+    // waiting for child to finish
+    int status;
+    BOOST_CHECK_NO_THROW(status = lxcpp::waitpid(pid));
+
+    ::close(fd[1]);
+    BOOST_CHECK_MESSAGE(status == 0, "child failed");
+}
+
+// this test case shows how to create container with network
+// Note: this test needs some preparation to successfuly connect an external site:
+// 1. allow network forwading (echo 1 > /proc/sys/net/ipv4/ip_forward)
+// 2. configure ip masquarading (iptables -t nat -A POSTROUTING -s 10.0.0.0/16 ! -d 10.0.0.0/16 -j MASQUERADE)
+BOOST_AUTO_TEST_CASE(NetworkNamespaceVETH)
+{
+    const char *vbr = "vbr";
+    const char *veth1 = "veth-ma";
+    const char *veth2 = "veth-sl";
+
+    int fd[2];
+    int r = ::pipe(fd);
+    BOOST_CHECK(r != -1);
+
+    pid_t pid = lxcpp::clone(child_exec, fd, CLONE_NEWNET);
+    ::close(fd[0]);
+
+    NetworkInterface br(vbr);
+    NetworkInterface v1(veth1);
+    NetworkInterface v2(veth2);
+
+    NetworkInterface("lo", pid).up();
+
+    // creating Bridge vbr
+    BOOST_CHECK_NO_THROW(br.create(InterfaceType::BRIDGE));
+    BOOST_CHECK_NO_THROW(br.up());
+    br.addInetAddr(InetAddr("10.0.0.1", 24));
+
+    // creating VETH pair  veth1 <-> veth2
+    BOOST_CHECK_NO_THROW(v1.create(InterfaceType::VETH, v2.getName()));
+
+    // add veth1 to bridge
+    BOOST_CHECK_NO_THROW(v1.addToBridge(br.getName()));
+
+    // move veth2 to network namespace (container)
+    BOOST_CHECK_NO_THROW(v2.moveToContainer(pid));
+    v2.up();
+    v2.addInetAddr(InetAddr("10.0.0.2", 24));
+
+    v1.up(); // after v2 up and configured
+
+    // add default route
+    v2.addRoute(Route{
+        InetAddr("10.0.0.1", 0), //dst - gateway
+        InetAddr("", 0),         //src - not specified (prefix=0)
+        0,
+        "",
+        RoutingTable::UNSPEC
+    });
+
+    //directives for child process
+    sendCmd(fd[1], "a"); // ip addr show
+    sendCmd(fd[1], "r"); // ip route list
+    sendCmd(fd[1], "c"); // connect extern (needs configured NAT)
+    //sendCmd(fd[1], "s"); // exec shell
+    sendCmd(fd[1], "0"); // exit
+
+    //  waiting for child to finish
+    int status;
+    BOOST_CHECK_NO_THROW(status = lxcpp::waitpid(pid));
+    ::close(fd[1]);
+    BOOST_CHECK_MESSAGE(status == 0, "child failed");
+
+    BOOST_CHECK_NO_THROW(br.destroy());
 }
 
 BOOST_AUTO_TEST_SUITE_END()