Possibility to remove ipv4/ipv6 addresses from interface 79/36979/6
authorMateusz Malicki <m.malicki2@samsung.com>
Tue, 17 Mar 2015 14:55:16 +0000 (15:55 +0100)
committerMateusz Malicki <m.malicki2@samsung.com>
Mon, 30 Mar 2015 10:36:46 +0000 (12:36 +0200)
[Feature]       Possibility to remove ipv4/ipv6 addresses from interface
[Cause]         N/A
[Solution]      Implemented: vsm_netdev_del_ipv4_addr, vsm_netdev_del_ipv6_addr
[Verification]  Build, run test

Change-Id: If1e43c73f443e5480e5f1794a1d1f29fa78c3dd3

17 files changed:
client/vasum-client-impl.cpp
client/vasum-client-impl.hpp
client/vasum-client.cpp
client/vasum-client.h
common/api/messages.hpp
server/host-connection.cpp
server/host-connection.hpp
server/host-dbus-definitions.hpp
server/netdev.cpp
server/netdev.hpp
server/zone-admin.cpp
server/zone-admin.hpp
server/zone.cpp
server/zone.hpp
server/zones-manager.cpp
server/zones-manager.hpp
tests/unit_tests/server/ut-zone.cpp

index 1de4de0..0c5482a 100644 (file)
@@ -744,6 +744,44 @@ VsmStatus Client::vsm_netdev_set_ipv6_addr(const char* zone,
     }
 }
 
+VsmStatus Client::vsm_netdev_del_ipv4_addr(const char* zone,
+                                           const char* netdevId,
+                                           struct in_addr* addr,
+                                           int prefix) noexcept
+{
+    std::string ip;
+    try {
+        //CIDR notation
+        ip = toString(addr) + "/" + to_string(prefix);
+    } catch(const std::exception& ex) {
+        mStatus = Status(VSMCLIENT_INVALID_ARGUMENT, ex.what());
+        return vsm_get_status();
+    }
+
+    GVariant* args_in = g_variant_new("(sss)", zone, netdevId, ip.c_str());
+    return callMethod(HOST_INTERFACE, api::host::METHOD_DELETE_NETDEV_IP_ADDRESS, args_in);
+}
+
+VsmStatus Client::vsm_netdev_del_ipv6_addr(const char* zone,
+                                           const char* netdevId,
+                                           struct in6_addr* addr,
+                                           int prefix) noexcept
+{
+
+    std::string ip;
+    try {
+        //CIDR notation
+        ip = toString(addr) + "/" + to_string(prefix);
+    } catch(const std::exception& ex) {
+        mStatus = Status(VSMCLIENT_INVALID_ARGUMENT, ex.what());
+        return vsm_get_status();
+    }
+
+    GVariant* args_in = g_variant_new("(sss)", zone, netdevId, ip.c_str());
+    return callMethod(HOST_INTERFACE, api::host::METHOD_DELETE_NETDEV_IP_ADDRESS, args_in);
+}
+
+
 VsmStatus Client::vsm_netdev_up(const char* zone, const char* netdevId) noexcept
 {
     try {
index 1493a00..4e01966 100644 (file)
@@ -235,6 +235,22 @@ public:
                                        int prefix) noexcept;
 
     /**
+     *  @see ::vsm_netdev_del_ipv4_addr
+     */
+    VsmStatus vsm_netdev_del_ipv4_addr(const char* zone,
+                                      const char* netdevId,
+                                      struct in_addr* addr,
+                                      int prefix) noexcept;
+
+    /**
+     *  @see ::vsm_netdev_del_ipv6_addr
+     */
+    VsmStatus vsm_netdev_del_ipv6_addr(const char* zone,
+                                      const char* netdevId,
+                                      struct in6_addr* addr,
+                                      int prefix) noexcept;
+
+    /**
      *  @see ::vsm_netdev_up
      */
     VsmStatus vsm_netdev_up(const char* zone, const char* netdevId) noexcept;
index 9c0ca60..c12e8c6 100644 (file)
@@ -250,6 +250,24 @@ API VsmStatus vsm_netdev_set_ipv6_addr(VsmClient client,
     return getClient(client).vsm_netdev_set_ipv6_addr(zone, netdevId, addr, prefix);
 }
 
+API VsmStatus vsm_netdev_del_ipv4_addr(VsmClient client,
+                                       const char* zone,
+                                       const char* netdevId,
+                                       struct in_addr* addr,
+                                       int prefix)
+{
+    return getClient(client).vsm_netdev_del_ipv4_addr(zone, netdevId, addr, prefix);
+}
+
+API VsmStatus vsm_netdev_del_ipv6_addr(VsmClient client,
+                                       const char* zone,
+                                       const char* netdevId,
+                                       struct in6_addr* addr,
+                                       int prefix)
+{
+    return getClient(client).vsm_netdev_del_ipv6_addr(zone, netdevId, addr, prefix);
+}
+
 API VsmStatus vsm_netdev_up(VsmClient client,
                             const char* zone,
                             const char* netdevId)
index 18af990..c252b32 100644 (file)
@@ -552,6 +552,38 @@ VsmStatus vsm_netdev_set_ipv6_addr(VsmClient client,
                                    int prefix);
 
 /**
+ * Remove ipv4 address from netdev
+ *
+ * @param[in] client vasum-server's client
+ * @param[in] zone zone name
+ * @param[in] netdevId network device id
+ * @param[in] addr ipv4 address
+ * @param[in] prefix bit-length of the network prefix
+ * @return status of this function call
+ */
+VsmStatus vsm_netdev_del_ipv4_addr(VsmClient client,
+                                   const char* zone,
+                                   const char* netdevId,
+                                   struct in_addr* addr,
+                                   int prefix);
+
+/**
+ * Remove ipv6 address from netdev
+ *
+ * @param[in] client vasum-server's client
+ * @param[in] zone zone name
+ * @param[in] netdevId network device id
+ * @param[in] addr ipv6 address
+ * @param[in] prefix bit-length of the network prefix
+ * @return status of this function call
+ */
+VsmStatus vsm_netdev_del_ipv6_addr(VsmClient client,
+                                   const char* zone,
+                                   const char* netdevId,
+                                   struct in6_addr* addr,
+                                   int prefix);
+
+/**
  * Turn up a network device in the zone
  *
  * @param[in] client vasum-server's client
@@ -750,9 +782,9 @@ VsmStatus vsm_remove_declaration(VsmClient client,
  * @param data custom user's data pointer passed to vsm_add_notification_callback()
  */
 typedef void (*VsmNotificationCallback)(const char* zone,
-                                       const char* application,
-                                       const char* message,
-                                       void* data);
+                                        const char* application,
+                                        const char* message,
+                                        void* data);
 /**
  * Send message to active zone.
  *
index a567975..de193bd 100644 (file)
@@ -146,6 +146,19 @@ struct CreateNetDevMacvlanIn {
     )
 };
 
+struct DeleteNetdevIpAddressIn {
+    std::string zone;
+    std::string netdev;
+    std::string ip;
+
+    CONFIG_REGISTER
+    (
+        zone,
+        netdev,
+        ip
+    )
+};
+
 struct DeclareFileIn {
     std::string zone;
     int32_t type;
index 993b63d..f3cbfa0 100644 (file)
@@ -167,6 +167,11 @@ void HostConnection::setDestroyNetdevCallback(const DestroyNetdevCallback& callb
     mDestroyNetdevCallback = callback;
 }
 
+void HostConnection::setDeleleNetdevIpAddressCallback(const DeleteNetdevIpAddressCallback& callback)
+{
+    mDeleteNetdevIpAddressCallback = callback;
+}
+
 void HostConnection::setDeclareFileCallback(const DeclareFileCallback& callback)
 {
     mDeclareFileCallback = callback;
@@ -397,6 +402,15 @@ void HostConnection::onMessageCall(const std::string& objectPath,
         }
     }
 
+    if (methodName == api::host::METHOD_DELETE_NETDEV_IP_ADDRESS) {
+        api::DeleteNetdevIpAddressIn data;
+        config::loadFromGVariant(parameters, data);
+        if (mDeleteNetdevIpAddressCallback) {
+            auto rb = std::make_shared<api::DbusMethodResultBuilder<api::Void>>(result);
+            mDeleteNetdevIpAddressCallback(data, rb);
+        }
+    }
+
     if (methodName == api::host::METHOD_DECLARE_FILE) {
         api::DeclareFileIn data;
         config::loadFromGVariant(parameters, data);
index 4cb1c8e..4ad07c8 100644 (file)
@@ -82,6 +82,9 @@ public:
     typedef std::function<void(const api::CreateNetDevPhysIn& dataIn,
                                api::MethodResultBuilder::Pointer result
                               )> CreateNetdevPhysCallback;
+    typedef std::function<void(const api::DeleteNetdevIpAddressIn& dataIn,
+                               api::MethodResultBuilder::Pointer result
+                              )> DeleteNetdevIpAddressCallback;
     typedef std::function<void(const api::DestroyNetDevIn& dataIn,
                                api::MethodResultBuilder::Pointer result
                               )> DestroyNetdevCallback;
@@ -194,6 +197,11 @@ public:
     void setDestroyNetdevCallback(const DestroyNetdevCallback& callback);
 
     /**
+     * Register a callback called to remove ip address from netdev
+     */
+    void setDeleleNetdevIpAddressCallback(const DeleteNetdevIpAddressCallback& callback);
+
+    /**
      * Register a callback called to declare file
      */
     void setDeclareFileCallback(const DeclareFileCallback& callback);
@@ -291,6 +299,7 @@ private:
     CreateNetdevMacvlanCallback mCreateNetdevMacvlanCallback;
     CreateNetdevPhysCallback mCreateNetdevPhysCallback;
     DestroyNetdevCallback mDestroyNetdevCallback;
+    DeleteNetdevIpAddressCallback mDeleteNetdevIpAddressCallback;
     DeclareFileCallback mDeclareFileCallback;
     DeclareMountCallback mDeclareMountCallback;
     DeclareLinkCallback mDeclareLinkCallback;
index 2ac5745..1e6fecc 100644 (file)
@@ -32,39 +32,40 @@ namespace vasum {
 namespace api {
 namespace host {
 
-const std::string BUS_NAME                     = "org.tizen.vasum.host";
-const std::string OBJECT_PATH                  = "/org/tizen/vasum/host";
-const std::string INTERFACE                    = "org.tizen.vasum.host.manager";
+const std::string BUS_NAME                        = "org.tizen.vasum.host";
+const std::string OBJECT_PATH                     = "/org/tizen/vasum/host";
+const std::string INTERFACE                       = "org.tizen.vasum.host.manager";
 
-const std::string ERROR_ZONE_NOT_RUNNING       = "org.tizen.vasum.host.Error.ZonesNotRunning";
+const std::string ERROR_ZONE_NOT_RUNNING          = "org.tizen.vasum.host.Error.ZonesNotRunning";
 
-const std::string METHOD_GET_ZONE_DBUSES       = "GetZoneDbuses";
-const std::string METHOD_GET_ZONE_ID_LIST      = "GetZoneIds";
-const std::string METHOD_GET_ACTIVE_ZONE_ID    = "GetActiveZoneId";
-const std::string METHOD_GET_ZONE_INFO         = "GetZoneInfo";
-const std::string METHOD_SET_NETDEV_ATTRS      = "SetNetdevAttrs";
-const std::string METHOD_GET_NETDEV_ATTRS      = "GetNetdevAttrs";
-const std::string METHOD_GET_NETDEV_LIST       = "GetNetdevList";
-const std::string METHOD_CREATE_NETDEV_VETH    = "CreateNetdevVeth";
-const std::string METHOD_CREATE_NETDEV_MACVLAN = "CreateNetdevMacvlan";
-const std::string METHOD_CREATE_NETDEV_PHYS    = "CreateNetdevPhys";
-const std::string METHOD_DESTROY_NETDEV        = "DestroyNetdev";
-const std::string METHOD_DECLARE_FILE          = "DeclareFile";
-const std::string METHOD_DECLARE_MOUNT         = "DeclareMount";
-const std::string METHOD_DECLARE_LINK          = "DeclareLink";
-const std::string METHOD_GET_DECLARATIONS      = "GetDeclarations";
-const std::string METHOD_REMOVE_DECLARATION    = "RemoveDeclaration";
-const std::string METHOD_SET_ACTIVE_ZONE       = "SetActiveZone";
-const std::string METHOD_CREATE_ZONE           = "CreateZone";
-const std::string METHOD_DESTROY_ZONE          = "DestroyZone";
-const std::string METHOD_SHUTDOWN_ZONE         = "ShutdownZone";
-const std::string METHOD_START_ZONE            = "StartZone";
-const std::string METHOD_LOCK_ZONE             = "LockZone";
-const std::string METHOD_UNLOCK_ZONE           = "UnlockZone";
-const std::string METHOD_GRANT_DEVICE          = "GrantDevice";
-const std::string METHOD_REVOKE_DEVICE         = "RevokeDevice";
+const std::string METHOD_GET_ZONE_DBUSES          = "GetZoneDbuses";
+const std::string METHOD_GET_ZONE_ID_LIST         = "GetZoneIds";
+const std::string METHOD_GET_ACTIVE_ZONE_ID       = "GetActiveZoneId";
+const std::string METHOD_GET_ZONE_INFO            = "GetZoneInfo";
+const std::string METHOD_SET_NETDEV_ATTRS         = "SetNetdevAttrs";
+const std::string METHOD_GET_NETDEV_ATTRS         = "GetNetdevAttrs";
+const std::string METHOD_GET_NETDEV_LIST          = "GetNetdevList";
+const std::string METHOD_CREATE_NETDEV_VETH       = "CreateNetdevVeth";
+const std::string METHOD_CREATE_NETDEV_MACVLAN    = "CreateNetdevMacvlan";
+const std::string METHOD_CREATE_NETDEV_PHYS       = "CreateNetdevPhys";
+const std::string METHOD_DESTROY_NETDEV           = "DestroyNetdev";
+const std::string METHOD_DELETE_NETDEV_IP_ADDRESS = "DeleteNetdevIpAddress";
+const std::string METHOD_DECLARE_FILE             = "DeclareFile";
+const std::string METHOD_DECLARE_MOUNT            = "DeclareMount";
+const std::string METHOD_DECLARE_LINK             = "DeclareLink";
+const std::string METHOD_GET_DECLARATIONS         = "GetDeclarations";
+const std::string METHOD_REMOVE_DECLARATION       = "RemoveDeclaration";
+const std::string METHOD_SET_ACTIVE_ZONE          = "SetActiveZone";
+const std::string METHOD_CREATE_ZONE              = "CreateZone";
+const std::string METHOD_DESTROY_ZONE             = "DestroyZone";
+const std::string METHOD_SHUTDOWN_ZONE            = "ShutdownZone";
+const std::string METHOD_START_ZONE               = "StartZone";
+const std::string METHOD_LOCK_ZONE                = "LockZone";
+const std::string METHOD_UNLOCK_ZONE              = "UnlockZone";
+const std::string METHOD_GRANT_DEVICE             = "GrantDevice";
+const std::string METHOD_REVOKE_DEVICE            = "RevokeDevice";
 
-const std::string SIGNAL_ZONE_DBUS_STATE       = "ZoneDbusState";
+const std::string SIGNAL_ZONE_DBUS_STATE          = "ZoneDbusState";
 
 
 const std::string DEFINITION =
@@ -125,6 +126,11 @@ const std::string DEFINITION =
     "      <arg type='s' name='id' direction='in'/>"
     "      <arg type='s' name='devId' direction='in'/>"
     "    </method>"
+    "    <method name='" + METHOD_DELETE_NETDEV_IP_ADDRESS + "'>"
+    "      <arg type='s' name='id' direction='in'/>"
+    "      <arg type='s' name='devId' direction='in'/>"
+    "      <arg type='s' name='ip' direction='in'/>"
+    "    </method>"
     "    <method name='" + METHOD_DECLARE_FILE + "'>"
     "      <arg type='s' name='zone' direction='in'/>"
     "      <arg type='i' name='type' direction='in'/>"
index da58641..38ccb88 100644 (file)
@@ -120,6 +120,11 @@ uint32_t getInterfaceIndex(const string& name, pid_t nsPid) {
     return infoPeer.ifi_index;
 }
 
+int getIpFamily(const std::string& ip)
+{
+    return ip.find(':') == std::string::npos ? AF_INET : AF_INET6;
+}
+
 void validateNetdevName(const string& name)
 {
     if (name.size() <= 1 || name.size() >= IFNAMSIZ) {
@@ -331,6 +336,36 @@ void  setIpAddresses(const pid_t nsPid,
     send(nlm, nsPid);
 }
 
+void deleteIpAddress(const pid_t nsPid,
+                     const uint32_t index,
+                     const std::string& ip,
+                     int prefixlen,
+                     int family)
+{
+    NetlinkMessage nlm(RTM_DELADDR, NLM_F_REQUEST | NLM_F_ACK);
+    ifaddrmsg infoAddr = utils::make_clean<ifaddrmsg>();
+    infoAddr.ifa_family = family;
+    infoAddr.ifa_index = index;
+    infoAddr.ifa_prefixlen = prefixlen;
+    nlm.put(infoAddr);
+    if (family == AF_INET6) {
+        in6_addr addr6;
+        if (inet_pton(AF_INET6, ip.c_str(), &addr6) != 1) {
+            throw VasumException("Can't delete ipv6 address");
+        };
+        nlm.put(IFA_ADDRESS, addr6);
+        nlm.put(IFA_LOCAL, addr6);
+    } else {
+        assert(family == AF_INET);
+        in_addr addr4;
+        if (inet_pton(AF_INET, ip.c_str(), &addr4) != 1) {
+            throw VasumException("Can't delete ipv4 address");
+        };
+        nlm.put(IFA_LOCAL, addr4);
+    }
+    send(nlm, nsPid);
+}
+
 } // namespace
 
 void createVeth(const pid_t& nsPid, const string& nsDev, const string& hostDev)
@@ -565,6 +600,27 @@ void setAttrs(const pid_t nsPid, const std::string& netdev, const Attrs& attrs)
     setIp(ipv6, infoPeer.ifi_index, AF_INET6);
 }
 
+void deleteIpAddress(const pid_t nsPid,
+                     const std::string& netdev,
+                     const std::string& ip)
+{
+    uint32_t index = getInterfaceIndex(netdev, nsPid);
+    size_t slash = ip.find('/');
+    if (slash == string::npos) {
+        LOGE("Wrong address format: it is not CIDR notation: can't find '/'");
+        throw VasumException("Wrong address format");
+    }
+    int prefixlen = 0;
+    try {
+        prefixlen = stoi(ip.substr(slash + 1));
+    } catch (const std::exception& ex) {
+        LOGE("Wrong address format: invalid prefixlen");
+        throw VasumException("Wrong address format: invalid prefixlen");
+    }
+    deleteIpAddress(nsPid, index, ip.substr(0, slash), prefixlen, getIpFamily(ip));
+}
+
+
 } //namespace netdev
 } //namespace vasum
 
index 5e5821d..b3c574d 100644 (file)
@@ -57,6 +57,11 @@ void createBridge(const std::string& netdev);
 Attrs getAttrs(const pid_t nsPid, const std::string& netdev);
 void setAttrs(const pid_t nsPid, const std::string& netdev, const Attrs& attrs);
 
+/**
+ * Remove ipv4/ipv6 address from interface
+ */
+void deleteIpAddress(const pid_t nsPid, const std::string& netdev, const std::string& ip);
+
 } //namespace netdev
 } //namespace vasum
 
index b584279..3098f29 100644 (file)
@@ -322,4 +322,9 @@ std::vector<std::string> ZoneAdmin::getNetdevList()
     return netdev::listNetdev(mZone.getInitPid());
 }
 
+void ZoneAdmin::deleteNetdevIpAddress(const std::string& netdev, const std::string& ip)
+{
+    netdev::deleteIpAddress(mZone.getInitPid(), netdev, ip);
+}
+
 } // namespace vasum
index db2b0f2..d1b179d 100644 (file)
@@ -169,6 +169,11 @@ public:
      */
     std::vector<std::string> getNetdevList();
 
+    /**
+     * Remove ipv4/ipv6 address from network device
+     */
+    void deleteNetdevIpAddress(const std::string& netdev, const std::string& ip);
+
 private:
     const ZoneConfig& mConfig;
     const ZoneDynamicConfig& mDynamicConfig;
index b0c3c28..e26da52 100644 (file)
@@ -513,4 +513,10 @@ std::vector<std::string> Zone::getNetdevList()
     return mAdmin->getNetdevList();
 }
 
+void Zone::deleteNetdevIpAddress(const std::string& netdev, const std::string& ip)
+{
+    Lock lock(mReconnectMutex);
+    mAdmin->deleteNetdevIpAddress(netdev, ip);
+}
+
 } // namespace vasum
index b981e9a..687077f 100644 (file)
@@ -311,6 +311,11 @@ public:
      */
     std::vector<std::string> getNetdevList();
 
+    /**
+     * Remove ipv4/ipv6 address from network device
+     */
+    void deleteNetdevIpAddress(const std::string& netdev, const std::string& ip);
+
 private:
     utils::Worker::Pointer mWorker;
     ZoneConfig mConfig;
index 67e2b88..4f34a76 100644 (file)
@@ -163,6 +163,9 @@ ZonesManager::ZonesManager(const std::string& configPath)
     mHostConnection.setDestroyNetdevCallback(bind(&ZonesManager::handleDestroyNetdevCall,
                                                   this, _1, _2));
 
+    mHostConnection.setDeleleNetdevIpAddressCallback(bind(&ZonesManager::handleDeleteNetdevIpAddressCall,
+                                                          this, _1, _2));
+
     mHostConnection.setDeclareFileCallback(bind(&ZonesManager::handleDeclareFileCall,
                                                 this, _1, _2));
 
@@ -945,6 +948,23 @@ void ZonesManager::handleDestroyNetdevCall(const api::DestroyNetDevIn& data,
     }
 }
 
+void ZonesManager::handleDeleteNetdevIpAddressCall(const api::DeleteNetdevIpAddressIn& data,
+                                                   api::MethodResultBuilder::Pointer result)
+{
+    LOGI("DelNetdevIpAddress call");
+    try {
+        Lock lock(mMutex);
+        getZone(data.zone).deleteNetdevIpAddress(data.netdev, data.ip);
+        result->setVoid();
+    } catch (const InvalidZoneIdException&) {
+        LOGE("No zone with id=" << data.zone);
+        result->setError(api::ERROR_INVALID_ID, "No such zone id");
+    } catch (const VasumException& ex) {
+        LOGE("Can't delete address: " << ex.what());
+        result->setError(api::ERROR_INTERNAL, ex.what());
+    }
+}
+
 void ZonesManager::handleDeclareFileCall(const api::DeclareFileIn& data,
                                          api::MethodResultBuilder::Pointer result)
 {
index ae89566..f789595 100644 (file)
@@ -187,6 +187,8 @@ private:
                                     api::MethodResultBuilder::Pointer result);
     void handleDestroyNetdevCall(const api::DestroyNetDevIn& data,
                                  api::MethodResultBuilder::Pointer result);
+    void handleDeleteNetdevIpAddressCall(const api::DeleteNetdevIpAddressIn& data,
+                                         api::MethodResultBuilder::Pointer result);
     void handleDeclareFileCall(const api::DeclareFileIn& data,
                                api::MethodResultBuilder::Pointer result);
     void handleDeclareMountCall(const api::DeclareMountIn& data,
index 722f32c..dc66753 100644 (file)
@@ -155,7 +155,7 @@ BOOST_AUTO_TEST_CASE(DbusConnection)
     c->stop(true);
 }
 
-// TODO: DbusReconnectionTest
+// TODO: DbusReconnection
 
 BOOST_AUTO_TEST_CASE(ListNetdev)
 {
@@ -208,7 +208,7 @@ BOOST_AUTO_TEST_CASE(CreateNetdevMacvlan)
     BOOST_CHECK(find(netdevs.begin(), netdevs.end(), ZONE_NETDEV) != netdevs.end());
 }
 
-BOOST_AUTO_TEST_CASE(GetNetdevAttrsTest)
+BOOST_AUTO_TEST_CASE(GetNetdevAttrs)
 {
     setupBridge(BRIDGE_NAME);
     auto c = create(TEST_CONFIG_PATH);
@@ -240,7 +240,7 @@ BOOST_AUTO_TEST_CASE(GetNetdevAttrsTest)
     BOOST_CHECK(gotType);
 }
 
-BOOST_AUTO_TEST_CASE(SetNetdevAttrsTest)
+BOOST_AUTO_TEST_CASE(SetNetdevAttrs)
 {
     setupBridge(BRIDGE_NAME);
     auto c = create(TEST_CONFIG_PATH);
@@ -269,7 +269,7 @@ BOOST_AUTO_TEST_CASE(SetNetdevAttrsTest)
                             WhatEquals("Unsupported attribute: does_not_exists"));
 }
 
-BOOST_AUTO_TEST_CASE(SetNetdevIpv4Test)
+BOOST_AUTO_TEST_CASE(SetNetdevIpv4)
 {
     setupBridge(BRIDGE_NAME);
     auto c = create(TEST_CONFIG_PATH);
@@ -309,7 +309,7 @@ BOOST_AUTO_TEST_CASE(SetNetdevIpv4Test)
     BOOST_CHECK_EQUAL(gotIp, 3);
 }
 
-BOOST_AUTO_TEST_CASE(SetNetdevIpv6Test)
+BOOST_AUTO_TEST_CASE(SetNetdevIpv6)
 {
     setupBridge(BRIDGE_NAME);
     auto c = create(TEST_CONFIG_PATH);
@@ -349,4 +349,41 @@ BOOST_AUTO_TEST_CASE(SetNetdevIpv6Test)
     BOOST_CHECK_EQUAL(gotIp, 3);
 }
 
+BOOST_AUTO_TEST_CASE(DelNetdevIpAddress)
+{
+    auto contain = [](const ZoneAdmin::NetdevAttrs& container, const std::string& key) {
+        return container.end() != find_if(container.begin(),
+                                          container.end(),
+                                          [&](const ZoneAdmin::NetdevAttrs::value_type& value) {
+                                              return std::get<0>(value) == key;
+                                          });
+    };
+
+    setupBridge(BRIDGE_NAME);
+    auto c = create(TEST_CONFIG_PATH);
+    c->start();
+    ensureStarted();
+    c->createNetdevVeth(ZONE_NETDEV, BRIDGE_NAME);
+    ZoneAdmin::NetdevAttrs attrs;
+    attrs.push_back(std::make_tuple("ipv6", "ip:2001:db8::1,prefixlen:64"));
+    attrs.push_back(std::make_tuple("ipv4", "ip:192.168.4.1,prefixlen:24"));
+    c->setNetdevAttrs(ZONE_NETDEV, attrs);
+    attrs = c->getNetdevAttrs(ZONE_NETDEV);
+    BOOST_REQUIRE(contain(attrs, "ipv4"));
+    BOOST_REQUIRE(contain(attrs, "ipv6"));
+
+    c->deleteNetdevIpAddress(ZONE_NETDEV, "192.168.4.1/24");
+    attrs = c->getNetdevAttrs(ZONE_NETDEV);
+    BOOST_CHECK(!contain(attrs, "ipv4"));
+    BOOST_CHECK(contain(attrs, "ipv6"));
+
+    c->deleteNetdevIpAddress(ZONE_NETDEV, "2001:db8::1/64");
+    attrs = c->getNetdevAttrs(ZONE_NETDEV);
+    BOOST_REQUIRE(!contain(attrs, "ipv4"));
+    BOOST_REQUIRE(!contain(attrs, "ipv6"));
+
+    BOOST_CHECK_THROW(c->deleteNetdevIpAddress(ZONE_NETDEV, "192.168.4.1/24"), VasumException);
+    BOOST_CHECK_THROW(c->deleteNetdevIpAddress(ZONE_NETDEV, "2001:db8::1/64"), VasumException);
+}
+
 BOOST_AUTO_TEST_SUITE_END()