lxcpp: network implementation (part 3) 32/48832/18
authorKrzysztof Dynowski <k.dynowski@samsung.com>
Thu, 3 Sep 2015 07:34:37 +0000 (09:34 +0200)
committerKrzysztof Dynowski <k.dynowski@samsung.com>
Thu, 15 Oct 2015 10:14:56 +0000 (12:14 +0200)
[Feature]       Network implementation for lxcpp (routing table manipulation)
[Cause]         N/A
[Solution]      N/A
[Verification]  Build, install, run tests

Change-Id: I983a4b565c4b9e1b07ae8ab568445729c0f80eb8

common/utils/execute.cpp
libs/lxcpp/container-impl.cpp
libs/lxcpp/network.cpp
libs/lxcpp/network.hpp
tests/unit_tests/lxcpp/ut-network.cpp

index d047a2e..29c888e 100644 (file)
@@ -99,6 +99,7 @@ bool executeAndWait(const char* fname, const char* const* argv, int& status)
 
     bool success = executeAndWait([=]() {
         execv(fname, const_cast<char* const*>(argv));
+        LOGE("execv failed: " << getSystemErrorMessage());
         _exit(EXIT_FAILURE);
     }, status);
     if (!success) {
index 84f9b44..e19012c 100644 (file)
@@ -240,7 +240,7 @@ NetworkInterfaceInfo ContainerImpl::getInterfaceInfo(const std::string& ifname)
     std::vector<InetAddr> addrs;
     std::string macaddr;
     int mtu = 0, flags = 0;
-    Attrs attrs = ni.getAttrs();
+    const Attrs& attrs = ni.getAttrs();
     for (const Attr& a : attrs) {
         switch (a.name) {
         case AttrName::MAC:
index 1214e5c..7b9c0b3 100644 (file)
@@ -70,7 +70,18 @@ uint32_t getInterfaceIndex(const std::string& name)
     return index;
 }
 
-uint32_t getInterfaceIndex(const std::string& name, pid_t pid)
+std::string getInterfaceName(uint32_t index)
+{
+    char buf[IFNAMSIZ];
+    if (::if_indextoname(index, buf) == NULL) {
+        const std::string msg = "No interface for index: " + std::to_string(index);
+        LOGE(msg);
+        throw NetworkException(msg);
+    }
+    return buf;
+}
+
+uint32_t getInterfaceIndex(pid_t pid, const std::string& name)
 {
     NetlinkMessage nlm(RTM_GETLINK, NLM_F_REQUEST | NLM_F_ACK);
     ifinfomsg info = utils::make_clean<ifinfomsg>();
@@ -81,7 +92,7 @@ uint32_t getInterfaceIndex(const std::string& name, pid_t pid)
 
     NetlinkResponse response = send(nlm, pid);
     if (!response.hasMessage()) {
-        const std::string msg = "Can't get interface index";
+        const std::string msg = "Can't get interface index for " + name;
         LOGE(msg);
         throw NetworkException(msg);
     }
@@ -90,7 +101,7 @@ uint32_t getInterfaceIndex(const std::string& name, pid_t pid)
     return info.ifi_index;
 }
 
-void bridgeModify(const std::string& ifname, uint32_t masterIndex) {
+void bridgeModify(pid_t pid, const std::string& ifname, uint32_t masterIndex) {
     NetlinkMessage nlm(RTM_SETLINK, NLM_F_REQUEST | NLM_F_ACK);
     ifinfomsg info = utils::make_clean<ifinfomsg>();
     info.ifi_family = AF_UNSPEC;
@@ -98,12 +109,12 @@ void bridgeModify(const std::string& ifname, uint32_t masterIndex) {
     info.ifi_index = getInterfaceIndex(ifname);
     nlm.put(info)
         .put(IFLA_MASTER, masterIndex);
-    send(nlm);
+    send(nlm, pid);
 }
 
-void getAddressList(std::vector<InetAddr>& addrs, int family, const std::string& ifname, pid_t pid)
+void getAddressList(pid_t pid, std::vector<InetAddr>& addrs, int family, const std::string& ifname)
 {
-    uint32_t index = getInterfaceIndex(ifname, pid);
+    uint32_t index = getInterfaceIndex(pid, ifname);
     NetlinkMessage nlm(RTM_GETADDR, NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP);
     ifaddrmsg infoAddr = utils::make_clean<ifaddrmsg>();
     infoAddr.ifa_family = family; //test AF_PACKET to get all AF_INET* ?
@@ -174,6 +185,33 @@ void getAddressList(std::vector<InetAddr>& addrs, int family, const std::string&
         response.fetchNextMessage();
     }
 }
+
+/*
+ * convert string-hex to byte array
+ */
+void toMacAddressArray(const std::string& s, char *buf, size_t bs)
+{
+    static const std::string hex = "0123456789ABCDEF";
+    int nibble = 0; //nibble counter, one nibble = 4 bits (or one hex digit)
+    int v = 0;
+    ::memset(buf, 0, bs);
+    for (size_t i = 0; i < s.length() && bs > 0; ++i) {
+        const char c = s.at(i);
+        size_t pos = hex.find(c);
+        if (pos == std::string::npos) { // ignore eny non-hex character
+            continue;
+        }
+        v <<= 4;
+        v |= pos; // add nibble
+        if (++nibble == 2) { // two nibbles collected (one byte)
+            *buf++ = v; // assign the byte
+            --bs;       // decrease space left
+            v = 0;      // reset byte and nibble counter
+            nibble = 0;
+        }
+    }
+}
+
 } // anonymous namespace
 
 
@@ -236,7 +274,9 @@ std::string toString(const InetAddr& a) {
     return "";
 }
 
-InetAddr::InetAddr(uint32_t f, int p, const std::string& a)
+InetAddr::InetAddr(const std::string& a, unsigned p, uint32_t f) :
+    prefix(p),
+    flags(f)
 {
     if (a.find(":") != std::string::npos) {
         setType(InetAddrType::IPV6);
@@ -245,8 +285,6 @@ InetAddr::InetAddr(uint32_t f, int p, const std::string& a)
         setType(InetAddrType::IPV4);
         fromString(a, getAddr<in_addr>());
     }
-    flags = f;
-    prefix = p;
 }
 
 void NetworkInterface::create(InterfaceType type,
@@ -285,7 +323,7 @@ void NetworkInterface::createVeth(const std::string& peerif)
                 .endNested()
             .endNested()
         .endNested();
-    send(nlm);
+    send(nlm, mContainerPid);
 }
 
 void NetworkInterface::createBridge()
@@ -304,7 +342,7 @@ void NetworkInterface::createBridge()
             .endNested()
         .endNested()
         .put(IFLA_IFNAME, mIfname); //bridge name (will be created)
-    send(nlm);
+    send(nlm, mContainerPid);
 }
 
 void NetworkInterface::createMacVLan(const std::string& maserif, MacVLanMode mode)
@@ -323,7 +361,7 @@ void NetworkInterface::createMacVLan(const std::string& maserif, MacVLanMode mod
         .endNested()
         .put(IFLA_LINK, index)      //master index
         .put(IFLA_IFNAME, mIfname); //slave name (will be created)
-    send(nlm);
+    send(nlm, mContainerPid);
 }
 
 void NetworkInterface::moveToContainer(pid_t pid)
@@ -334,7 +372,7 @@ void NetworkInterface::moveToContainer(pid_t pid)
     info.ifi_index = getInterfaceIndex(mIfname);
     nlm.put(info)
         .put(IFLA_NET_NS_PID, pid);
-    send(nlm);
+    send(nlm, mContainerPid);
     mContainerPid = pid;
 }
 
@@ -345,28 +383,25 @@ void NetworkInterface::destroy()
     ifinfomsg info = utils::make_clean<ifinfomsg>();
     info.ifi_family = AF_UNSPEC;
     info.ifi_change = CHANGE_FLAGS_DEFAULT;
-    info.ifi_index = getInterfaceIndex(mIfname, mContainerPid);
+    info.ifi_index = getInterfaceIndex(mContainerPid, mIfname);
     nlm.put(info)
         .put(IFLA_IFNAME, mIfname);
-    send(nlm);
+    send(nlm, mContainerPid);
 }
 
 NetStatus NetworkInterface::status() const
 {
-    NetlinkMessage nlm(RTM_GETLINK, NLM_F_REQUEST | NLM_F_ACK);
-    ifinfomsg info = utils::make_clean<ifinfomsg>();
-    info.ifi_family = AF_UNSPEC;
-    info.ifi_change = CHANGE_FLAGS_DEFAULT;
-    nlm.put(info)
-        .put(IFLA_IFNAME, mIfname);
-
-    NetlinkResponse response = send(nlm, mContainerPid);
-    if (!response.hasMessage()) {
-        throw NetworkException("Can't get interface information");
-    }
-
-    response.fetch(info);
-    return (info.ifi_flags & IFF_UP) != 0 ? NetStatus::UP : NetStatus::DOWN;
+    const Attrs& attrs = getAttrs();
+    NetStatus st = NetStatus::DOWN;
+    for (const Attr& a : attrs)
+        if (a.name == AttrName::FLAGS) {
+            uint32_t f = stoul(a.value);
+            if (f & IFF_UP) {
+                st = NetStatus::UP;
+                break;
+            }
+        }
+    return st;
 }
 
 void NetworkInterface::renameFrom(const std::string& oldif)
@@ -374,7 +409,7 @@ void NetworkInterface::renameFrom(const std::string& oldif)
     NetlinkMessage nlm(RTM_SETLINK, NLM_F_REQUEST | NLM_F_ACK);
     ifinfomsg info = utils::make_clean<ifinfomsg>();
     info.ifi_family = AF_UNSPEC;
-    info.ifi_index = getInterfaceIndex(oldif, mContainerPid);
+    info.ifi_index = getInterfaceIndex(mContainerPid, oldif);
     info.ifi_change = CHANGE_FLAGS_DEFAULT;
 
     nlm.put(info)
@@ -384,15 +419,14 @@ void NetworkInterface::renameFrom(const std::string& oldif)
 
 void NetworkInterface::addToBridge(const std::string& bridge)
 {
-    bridgeModify(mIfname, getInterfaceIndex(bridge));
+    bridgeModify(mContainerPid, mIfname, getInterfaceIndex(mContainerPid, bridge));
 }
 
 void NetworkInterface::delFromBridge()
 {
-    bridgeModify(mIfname, 0);
+    bridgeModify(mContainerPid, mIfname, 0);
 }
 
-
 void NetworkInterface::setAttrs(const Attrs& attrs)
 {
     if (attrs.empty()) {
@@ -402,7 +436,7 @@ void NetworkInterface::setAttrs(const Attrs& attrs)
     //TODO check this: NetlinkMessage nlm(RTM_SETLINK, NLM_F_REQUEST | NLM_F_ACK);
     NetlinkMessage nlm(RTM_NEWLINK, NLM_F_REQUEST | NLM_F_CREATE | NLM_F_ACK);
     ifinfomsg info = utils::make_clean<ifinfomsg>();
-    info.ifi_index = getInterfaceIndex(mIfname, mContainerPid);
+    info.ifi_index = getInterfaceIndex(mContainerPid, mIfname);
     info.ifi_family = AF_UNSPEC;
     info.ifi_change = CHANGE_FLAGS_DEFAULT;
 
@@ -437,7 +471,9 @@ void NetworkInterface::setAttrs(const Attrs& attrs)
         nlm.put<uint32_t>(IFLA_TXQLEN, txq);
     }
     if (!mac.empty()) {
-        nlm.put(IFLA_ADDRESS, mac);
+        char buf[6];
+        toMacAddressArray(mac, buf, 6);
+        nlm.put(IFLA_ADDRESS, buf);
     }
 
     NetlinkResponse response = send(nlm, mContainerPid);
@@ -460,8 +496,8 @@ Attrs NetworkInterface::getAttrs() const
         throw NetworkException("Can't get interface information");
     }
 
-    Attrs attrs;
     response.fetch(info);
+    Attrs attrs;
     attrs.push_back(Attr{AttrName::FLAGS, std::to_string(info.ifi_flags)});
     attrs.push_back(Attr{AttrName::TYPE, std::to_string(info.ifi_type)});
 
@@ -471,7 +507,7 @@ Attrs NetworkInterface::getAttrs() const
          * a few types of networks require 64-bit addresses instead.
          */
         std::string mac;
-        uint32_t mtu, link, txq;
+        uint32_t tmp;
         int attrType = response.getAttributeType();
         switch (attrType) {
         case IFLA_ADDRESS: //1
@@ -479,17 +515,18 @@ Attrs NetworkInterface::getAttrs() const
             attrs.push_back(Attr{AttrName::MAC, utils::toHexString(mac.c_str(), mac.size())});
             break;
         case IFLA_MTU: //4
-            response.fetch(IFLA_MTU, mtu);
-            attrs.push_back(Attr{AttrName::MTU, std::to_string(mtu)});
+            response.fetch(IFLA_MTU, tmp);
+            attrs.push_back(Attr{AttrName::MTU, std::to_string(tmp)});
             break;
         case IFLA_LINK://5
-            response.fetch(IFLA_LINK, link);
-            attrs.push_back(Attr{AttrName::LINK, std::to_string(link)});
+            response.fetch(IFLA_LINK, tmp);
+            attrs.push_back(Attr{AttrName::LINK, std::to_string(tmp)});
             break;
         case IFLA_TXQLEN://13
-            response.fetch(IFLA_TXQLEN, txq);
-            attrs.push_back(Attr{AttrName::TXQLEN, std::to_string(txq)});
+            response.fetch(IFLA_TXQLEN, tmp);
+            attrs.push_back(Attr{AttrName::TXQLEN, std::to_string(tmp)});
             break;
+        case IFLA_OPERSTATE://16  (IF_OPER_DOWN,...,IF_OPER_UP)
         case IFLA_BROADCAST://2 MAC broadcast
         case IFLA_IFNAME:   //3
         case IFLA_QDISC:    //6 queue discipline
@@ -500,6 +537,7 @@ Attrs NetworkInterface::getAttrs() const
         case IFLA_WIRELESS: //11
         case IFLA_PROTINFO: //12
         case IFLA_MAP:      //14
+        case IFLA_WEIGHT:   //15
         default:
             response.skipAttribute();
             break;
@@ -512,7 +550,7 @@ void NetworkInterface::addInetAddr(const InetAddr& addr)
 {
     NetlinkMessage nlm(RTM_NEWADDR, NLM_F_CREATE | NLM_F_REQUEST | NLM_F_ACK);
     ifaddrmsg infoAddr = utils::make_clean<ifaddrmsg>();
-    infoAddr.ifa_index = getInterfaceIndex(mIfname, mContainerPid);
+    infoAddr.ifa_index = getInterfaceIndex(mContainerPid, mIfname);
     infoAddr.ifa_family = addr.getType() == InetAddrType::IPV4 ? AF_INET : AF_INET6;
     infoAddr.ifa_prefixlen = addr.prefix;
     infoAddr.ifa_flags = addr.flags;
@@ -533,7 +571,7 @@ void NetworkInterface::delInetAddr(const InetAddr& addr)
 {
     NetlinkMessage nlm(RTM_DELADDR, NLM_F_REQUEST | NLM_F_ACK);
     ifaddrmsg infoAddr = utils::make_clean<ifaddrmsg>();
-    infoAddr.ifa_index = getInterfaceIndex(mIfname, mContainerPid);
+    infoAddr.ifa_index = getInterfaceIndex(mContainerPid, mIfname);
     infoAddr.ifa_family = addr.getType() == InetAddrType::IPV4 ? AF_INET : AF_INET6;
     infoAddr.ifa_prefixlen = addr.prefix;
     infoAddr.ifa_flags = addr.flags;
@@ -553,10 +591,250 @@ void NetworkInterface::delInetAddr(const InetAddr& addr)
 std::vector<InetAddr> NetworkInterface::getInetAddressList() const
 {
     std::vector<InetAddr> addrs;
-    getAddressList(addrs, AF_UNSPEC, mIfname, mContainerPid);
+    getAddressList(mContainerPid, addrs, AF_UNSPEC, mIfname);
     return addrs;
 }
 
+static rt_class_t getRoutingTableClass(const RoutingTable rt)
+{
+    switch (rt) {
+    case RoutingTable::UNSPEC:
+        return RT_TABLE_UNSPEC; //0
+    case RoutingTable::COMPAT:
+        return RT_TABLE_COMPAT; //252
+    case RoutingTable::DEFAULT:
+        return RT_TABLE_DEFAULT;//253
+    case RoutingTable::MAIN:
+        return RT_TABLE_MAIN;   //254
+    case RoutingTable::LOCAL:
+        return RT_TABLE_LOCAL;  //255
+    default: //all other are user tables (1...251), but I use only '1'
+        return static_cast<rt_class_t>(1);
+    }
+}
+static RoutingTable getRoutingTable(unsigned tbl)
+{
+    switch (tbl) {
+    case RT_TABLE_UNSPEC:
+        return RoutingTable::UNSPEC;
+    case RT_TABLE_COMPAT:
+        return RoutingTable::COMPAT;
+    case RT_TABLE_DEFAULT:
+        return RoutingTable::DEFAULT;
+    case RT_TABLE_MAIN:
+        return RoutingTable::MAIN;
+    case RT_TABLE_LOCAL:
+        return RoutingTable::LOCAL;
+    default:
+        return RoutingTable::USER;
+    }
+}
+
+void NetworkInterface::addRoute(const Route& route, const RoutingTable rt)
+{
+    InetAddrType type = route.dst.getType();
+    if (route.src.getType() != type) {
+        const std::string msg = "Family type must be the same";
+        LOGE(msg);
+        throw NetworkException(msg);
+    }
+    uint32_t index = getInterfaceIndex(mContainerPid, mIfname);
+    NetlinkMessage nlm(RTM_NEWROUTE, NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL | NLM_F_ACK);
+
+    rtmsg msg = utils::make_clean<rtmsg>();
+
+    if (type == InetAddrType::IPV6) {
+        msg.rtm_family = AF_INET6;
+    } else if (type == InetAddrType::IPV4) {
+        msg.rtm_family = AF_INET;
+    }
+    msg.rtm_table = getRoutingTableClass(rt);
+    msg.rtm_protocol = RTPROT_BOOT;
+    msg.rtm_scope = RT_SCOPE_UNIVERSE;
+    msg.rtm_type = RTN_UNICAST;
+    msg.rtm_dst_len = route.dst.prefix;
+
+    nlm.put(msg);
+
+    if (type == InetAddrType::IPV6) {
+        if (route.dst.prefix == 0)
+            nlm.put(RTA_GATEWAY, route.dst.getAddr<in6_addr>());
+        else
+            nlm.put(RTA_DST, route.dst.getAddr<in6_addr>());
+
+        if (route.src.prefix == 128) {
+            nlm.put(RTA_PREFSRC, route.src.getAddr<in6_addr>());
+        }
+    } else if (type == InetAddrType::IPV4) {
+        if (route.dst.prefix == 0)
+            nlm.put(RTA_GATEWAY, route.dst.getAddr<in_addr>());
+        else
+            nlm.put(RTA_DST, route.dst.getAddr<in_addr>());
+
+        if (route.src.prefix == 32) {
+            nlm.put(RTA_PREFSRC, route.src.getAddr<in_addr>());
+        }
+    }
+
+    nlm.put(RTA_OIF, index);
+
+    send(nlm, mContainerPid);
+}
+
+void NetworkInterface::delRoute(const Route& route, const RoutingTable rt)
+{
+    InetAddrType type = route.dst.getType();
+    if (route.src.getType() != type) {
+        const std::string msg = "Family type must be the same";
+        LOGE(msg);
+        throw NetworkException(msg);
+    }
+    uint32_t index = getInterfaceIndex(mContainerPid, mIfname);
+    NetlinkMessage nlm(RTM_DELROUTE, NLM_F_REQUEST | NLM_F_ACK);
+
+    rtmsg msg = utils::make_clean<rtmsg>();
+    msg.rtm_scope = RT_SCOPE_NOWHERE;
+    msg.rtm_table = getRoutingTableClass(rt);
+    msg.rtm_dst_len = route.dst.prefix;
+    if (type == InetAddrType::IPV6) {
+        msg.rtm_family = AF_INET6;
+    } else if (type == InetAddrType::IPV4) {
+        msg.rtm_family = AF_INET;
+    }
+    nlm.put(msg);
+
+    if (type == InetAddrType::IPV6) {
+        nlm.put(RTA_DST, route.dst.getAddr<in6_addr>());
+    } else if (type == InetAddrType::IPV4) {
+        nlm.put(RTA_DST, route.dst.getAddr<in_addr>());
+    }
+    nlm.put(RTA_OIF, index);
+
+    send(nlm, mContainerPid);
+}
+
+static std::vector<Route> getRoutesImpl(pid_t pid, rt_class_t tbl, const std::string& ifname, int family)
+{
+    uint32_t searchindex = 0;
+    NetlinkMessage nlm(RTM_GETROUTE, NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP);
+    ifinfomsg info = utils::make_clean<ifinfomsg>();
+    info.ifi_family = family;
+    info.ifi_change = CHANGE_FLAGS_DEFAULT;
+
+    nlm.put(info);
+
+    NetlinkResponse response = send(nlm, pid);
+
+    if (!ifname.empty()) {
+        searchindex = getInterfaceIndex(pid, ifname);
+    }
+
+    std::vector<Route> routes;
+    for ( ; response.hasMessage(); response.fetchNextMessage()) {
+        if (response.getMessageType() != RTM_NEWROUTE) {
+            LOGW("Not route info in response");
+            continue;
+        }
+        rtmsg rt;
+        response.fetch(rt);
+        if (tbl != RT_TABLE_UNSPEC && rt.rtm_table != tbl) {
+            continue;
+        }
+        if (rt.rtm_flags & RTM_F_CLONED) {
+            continue;
+        }
+
+        Route route;
+        uint32_t index = 0;
+        route.dst.flags = 0;
+        route.src.flags = 0;
+        route.dst.prefix = 0;
+        route.src.prefix = 0;
+        route.table = getRoutingTable(rt.rtm_table);
+
+        if (rt.rtm_family == AF_INET6) {
+            route.dst.setType(InetAddrType::IPV6);
+            route.src.setType(InetAddrType::IPV6);
+        } else if (rt.rtm_family == AF_INET) {
+            route.dst.setType(InetAddrType::IPV4);
+            route.src.setType(InetAddrType::IPV4);
+        } else {
+            const std::string msg = "Unsupported inet family";
+            LOGE(msg);
+            throw NetworkException(msg);
+        }
+
+        route.dst.prefix = rt.rtm_dst_len;
+        while (response.hasAttribute()) {
+            std::string tmpval;
+            uint32_t tmp;
+            int attrType = response.getAttributeType();
+            //int len = response.getAttributeLength();
+            switch (attrType) {
+            case RTA_DST:    // 1
+            case RTA_GATEWAY:// 5
+                if (route.dst.getType() == InetAddrType::IPV6) {
+                    response.fetch(attrType, route.dst.getAddr<in6_addr>());
+                } else {
+                    response.fetch(attrType, route.dst.getAddr<in_addr>());
+                }
+                break;
+            case RTA_SRC:    // 2
+            case RTA_PREFSRC:// 7
+                if (route.src.getType() == InetAddrType::IPV6) {
+                    response.fetch(attrType, route.src.getAddr<in6_addr>());
+                    route.src.prefix = 128;
+                } else {
+                    response.fetch(attrType, route.src.getAddr<in_addr>());
+                    route.src.prefix = 32;
+                }
+                break;
+            case RTA_OIF: //4
+                response.fetch(RTA_OIF, tmp);
+                index = tmp;
+                break;
+            case RTA_PRIORITY: // 6
+                response.fetch(RTA_PRIORITY, tmp);
+                route.metric = tmp;
+                break;
+            case RTA_TABLE:    //15  extends and ovewrites rt.rtm_table
+                response.fetch(RTA_TABLE, tmp);
+                route.table = getRoutingTable(tmp);
+                break;
+
+            case RTA_CACHEINFO://12
+                response.skipAttribute();
+                break;
+            case RTA_IIF:      // 3
+            case RTA_METRICS:  // 8  array of struct rtattr
+            case RTA_MULTIPATH:// 9  array of struct rtnexthop
+            case RTA_FLOW:     //11
+            default:
+                if (!searchindex) {
+                    response.fetch(attrType, tmpval);
+                    LOGW("rtAttr " << attrType << ":" <<
+                        utils::toHexString(tmpval.c_str(), tmpval.size()));
+                } else {
+                    response.skipAttribute();
+                }
+                break;
+            }
+        }
+        if (index == 0) continue;
+
+        if (searchindex == 0 || searchindex == index) {
+            route.ifname = getInterfaceName(index);
+            routes.push_back(route);
+        }
+    }
+    return routes;
+}
+
+std::vector<Route> NetworkInterface::getRoutes(const RoutingTable rt) const
+{
+    return getRoutesImpl(mContainerPid, getRoutingTableClass(rt), mIfname, AF_UNSPEC);
+}
+
 void NetworkInterface::up()
 {
     Attrs attrs;
@@ -617,4 +895,10 @@ std::vector<std::string> NetworkInterface::getInterfaces(pid_t initpid)
     return iflist;
 }
 
+std::vector<Route> NetworkInterface::getRoutes(pid_t initpid, const RoutingTable rt)
+{
+    rt_class_t tbl = getRoutingTableClass(rt);
+    return getRoutesImpl(initpid, tbl, "", AF_UNSPEC);
+}
+
 } // namespace lxcpp
index 0affc5f..ac94c44 100644 (file)
@@ -54,8 +54,8 @@ enum class InetAddrType {
  */
 class InetAddr {
 public:
-    InetAddr() {}
-    InetAddr(uint32_t flags, int prefix, const std::string& addr);
+    InetAddr() = default;
+    InetAddr(const std::string& addr, unsigned prefix, uint32_t flags=0);
 
     InetAddrType getType() const {
         return static_cast<InetAddrType>(type);
@@ -77,8 +77,8 @@ public:
         return *(reinterpret_cast<const T*>(v));
     }
 
+    unsigned prefix;
     uint32_t flags;
-    int prefix;
 
     CONFIG_REGISTER
     (
@@ -127,7 +127,9 @@ enum class RoutingTable {
     LOCAL,
     USER,
 };
-inline std::string toString(RoutingTable rt) {
+
+inline std::string toString(const RoutingTable rt)
+{
     switch (rt) {
     case RoutingTable::UNSPEC:
         return "unspec";
@@ -144,6 +146,14 @@ inline std::string toString(RoutingTable rt) {
     }
 }
 
+struct Route {
+    InetAddr dst;
+    InetAddr src;
+    unsigned metric;
+    std::string ifname;
+    RoutingTable table;
+};
+
 enum class AttrName {
     MAC,
     FLAGS,
@@ -205,10 +215,10 @@ std::string toString(const InetAddr& a);
  * operates on netlink device
  */
 class NetworkInterface {
+    //TODO implement Netlink singleton per pid
 public:
     /**
      * Create network interface object for the ifname in the container (network namespace)
-     * Note: pid=0 is kernel
      */
     NetworkInterface(const std::string& ifname, pid_t pid = 0) :
         mIfname(ifname),
@@ -224,7 +234,8 @@ public:
     NetStatus status() const;
 
     /**
-     * Create network interface in container identified by mContainerPid
+     * Create network interface in container identified by @mContainerPid
+     *
      * Equivalent to: ip link add @mIfname type @type [...]
      *   Create pair of virtual ethernet interfaces
      *      ip link add @mIfname type veth peer name @peerif
@@ -233,7 +244,7 @@ public:
      *   Create psedo-ethernet interface on existing one
      *      ip link add @mIfname type macvlan link @peerif [mode @a mode]
      */
-    void create(InterfaceType type, const std::string& peerif, MacVLanMode mode = MacVLanMode::PRIVATE);
+    void create(InterfaceType type, const std::string& peerif = "", MacVLanMode mode = MacVLanMode::PRIVATE);
 
     /**
      * Delete interface
@@ -243,9 +254,9 @@ public:
 
     /**
      * Move interface to container
-     * Equivalent to: ip link set dev @hostif netns @mContainerPid
+     * Equivalent to: ip link set dev @mIfname netns @pid
      */
-    void moveToContainer(pid_t p);
+    void moveToContainer(pid_t pid);
 
     /**
      * Rename interface name
@@ -291,8 +302,26 @@ public:
     std::vector<InetAddr> getInetAddressList() const;
 
     /**
+     * Add route to specified routing table
+     * Equivalent to: ip route add @route.dst.addr/@route.dst.prefix dev @mIfname (if route.src.prefix=0)
+     */
+    void addRoute(const Route& route, const RoutingTable rt = RoutingTable::MAIN);
+
+    /**
+     * Remove route from specified routing table
+     * Equivalent to: ip route del @route.dst.addr dev @mIfname
+     */
+    void delRoute(const Route& route, const RoutingTable rt = RoutingTable::MAIN);
+
+    /**
+     * Retrieve routing table for the interface
+     * Equivalent to: ip route show dev @mIfname table @rt
+     */
+    std::vector<Route> getRoutes(const RoutingTable rt = RoutingTable::MAIN) const;
+
+    /**
      * Set interface up
-     * Equivalent to: ip link set @mInface up
+     * Equivalent to: ip link set @mIfname up
      */
     void up();
 
@@ -304,20 +333,26 @@ public:
 
     /**
      * Set MAC address attribute
-     * Equivalent to: ip link set @mIface address @macaddr
+     * Equivalent to: ip link set @mIfname address @macaddr
      * @macaddr in format AA:BB:CC:DD:FF:GG
+     *
+     * Note: two lower bits of first byte (leftmost) specifies MAC address class:
+     *       b1: 0=unicast, 1=broadcast
+     *       b2: 0=global,  1=local
+     *       in most cases should be b2=0, b1=1
+     *       (see: https://en.wikipedia.org/wiki/MAC_address)
      */
     void setMACAddress(const std::string& macaddr);
 
     /**
      * Set MTU attribute
-     * Equivalent to: ip link set @mIface mtu @mtu
+     * Equivalent to: ip link set @mIfname mtu @mtu
      */
     void setMTU(int mtu);
 
     /**
      * Set TxQ attribute
-     * Equivalent to: ip link set @mIface txqueue @txlen
+     * Equivalent to: ip link set @mIfname txqueue @txlen
      */
     void setTxLength(int txlen);
 
@@ -327,11 +362,19 @@ public:
      */
     static std::vector<std::string> getInterfaces(pid_t initpid);
 
+    /**
+     * Get list of routes (specified routing table)
+     * Equivalent to: ip route show table @rt
+     */
+    static std::vector<Route> getRoutes(pid_t initpid, const RoutingTable rt = RoutingTable::MAIN);
+
 private:
     void createVeth(const std::string& peerif);
     void createBridge();
     void createMacVLan(const std::string& masterif, MacVLanMode mode);
 
+    void modifyRoute(int cmd, const InetAddr& src, const InetAddr& dst);
+
     const std::string mIfname; ///< network interface name inside zone
     pid_t mContainerPid;       ///< Container pid to operate on (0 means kernel)
 };
index f797a6c..a4741f6 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 <net/if.h>
 
-namespace {
-
 using namespace lxcpp;
-using namespace config;
+
+namespace {
 
 struct Fixture {
     Fixture() {}
@@ -49,8 +51,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,31 +129,12 @@ 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)
@@ -101,8 +147,7 @@ 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);
 
@@ -120,30 +165,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 +193,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 +203,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()