diff --git a/src/ice_transport.cpp b/src/ice_transport.cpp
index 4ecdbcce67782de2d2919d39702fe394c431ddbc..9db5816406b6e3b457ec29eb8aff34040e057aab 100644
--- a/src/ice_transport.cpp
+++ b/src/ice_transport.cpp
@@ -26,8 +26,10 @@
 #include "upnp/upnp_control.h"
 
 #include <pjlib.h>
+#include <msgpack.hpp>
 
 #include <utility>
+#include <tuple>
 #include <algorithm>
 #include <sstream>
 #include <chrono>
@@ -438,56 +440,48 @@ IceTransport::start(const Attribute& rem_attrs,
     return true;
 }
 
-std::string
-IceTransport::unpackLine(std::vector<uint8_t>::const_iterator& begin,
-                         std::vector<uint8_t>::const_iterator& end)
-{
-    if (std::distance(begin, end) <= 0)
-        return {};
-
-    // Search for EOL
-    std::vector<uint8_t>::const_iterator line_end(begin);
-    while (line_end != end && *line_end != NEW_LINE && *line_end)
-        ++line_end;
-
-    if (std::distance(begin, line_end) <= 0)
-        return {};
-
-    std::string str(begin, line_end);
-
-    // Consume the new line character
-    if (std::distance(line_end, end) > 0)
-        ++line_end;
-
-    begin = line_end;
-    return str;
-}
-
 bool
 IceTransport::start(const std::vector<uint8_t>& rem_data)
 {
-    auto begin = rem_data.cbegin();
-    auto end = rem_data.cend();
-    auto rem_ufrag = unpackLine(begin, end);
-    auto rem_pwd = unpackLine(begin, end);
-    if (rem_pwd.empty() or rem_pwd.empty()) {
-        RING_ERR("ICE remote attributes parsing error");
-        return false;
-    }
+    std::string rem_ufrag;
+    std::string rem_pwd;
     std::vector<IceCandidate> rem_candidates;
+
+    auto data = reinterpret_cast<const char*>(rem_data.data());
+    auto size = rem_data.size();
+
     try {
-        while (true) {
-            IceCandidate candidate;
-            const auto line = unpackLine(begin, end);
-            if (line.empty())
-                break;
-            if (getCandidateFromSDP(line, candidate))
-                rem_candidates.push_back(candidate);
+        std::size_t offset = 0;
+        auto result = msgpack::unpack(data, size, offset);
+        auto version = result.get().as<uint8_t>();
+        RING_DBG("[ice:%p] rx msg v%u", this, version);
+        if (version == 1) {
+            result = msgpack::unpack(data, size, offset);
+            std::tie(rem_ufrag, rem_pwd) = result.get().as<std::pair<std::string, std::string>>();
+            result = msgpack::unpack(data, size, offset);
+            auto comp_cnt = result.get().as<uint8_t>();
+            while (comp_cnt-- > 0) {
+                result = msgpack::unpack(data, size, offset);
+                IceCandidate cand;
+                for (const auto& line : result.get().as<std::vector<std::string>>()) {
+                    if (getCandidateFromSDP(line, cand))
+                        rem_candidates.emplace_back(std::move(cand));
+                }
+            }
+        } else {
+            RING_ERR("[ice:%p] invalid msg version", this);
+            return false;
         }
-    } catch (std::exception& e) {
-        RING_ERR("ICE remote candidates parsing error");
+    } catch (const msgpack::unpack_error& e) {
+        RING_ERR("[ice:%p] remote msg unpack error: %s", this, e.what());
         return false;
     }
+
+    if (rem_pwd.empty() or rem_pwd.empty() or rem_candidates.empty()) {
+        RING_ERR("[ice:%p] invalid remote attributes", this);
+        return false;
+    }
+
     return start({rem_ufrag, rem_pwd}, rem_candidates);
 }
 
@@ -773,19 +767,20 @@ IceTransport::selectUPnPIceCandidates()
 }
 
 std::vector<uint8_t>
-IceTransport::getLocalAttributesAndCandidates() const
+IceTransport::packIceMsg() const
 {
+    static constexpr uint8_t ICE_MSG_VERSION = 1;
+
     if (not isInitialized())
         return {};
 
     std::stringstream ss;
-    ss << local_ufrag_ << NEW_LINE;
-    ss << local_pwd_ << NEW_LINE;
-    for (unsigned i=0; i<component_count_; i++) {
-        const auto& candidates = getLocalCandidates(i);
-        for (const auto& c : candidates)
-            ss << c << NEW_LINE;
-    }
+    msgpack::pack(ss, ICE_MSG_VERSION);
+    msgpack::pack(ss, std::make_pair(local_ufrag_, local_pwd_));
+    msgpack::pack(ss, static_cast<uint8_t>(component_count_));
+    for (unsigned i=0; i<component_count_; i++)
+        msgpack::pack(ss, getLocalCandidates(i));
+
     auto str(ss.str());
     return std::vector<uint8_t>(str.begin(), str.end());
 }
diff --git a/src/ice_transport.h b/src/ice_transport.h
index dd3b1aaa2562dd27598c92266d87b3ce06e01353..8a9a6c7b632871a5de3afc20c25ed422759d235f 100644
--- a/src/ice_transport.h
+++ b/src/ice_transport.h
@@ -188,7 +188,7 @@ class IceTransport {
         /**
          * Returns serialized ICE attributes and candidates.
          */
-        std::vector<uint8_t> getLocalAttributesAndCandidates() const;
+        std::vector<uint8_t> packIceMsg() const;
 
         bool getCandidateFromSDP(const std::string& line, IceCandidate& cand);
 
@@ -226,9 +226,6 @@ class IceTransport {
                                        pj_ice_strans_op op,
                                        pj_status_t status);
 
-        static std::string unpackLine(std::vector<uint8_t>::const_iterator& begin,
-                                      std::vector<uint8_t>::const_iterator& end);
-
         struct IceSTransDeleter {
                 void operator ()(pj_ice_strans* ptr) {
                     pj_ice_strans_stop_ice(ptr);
diff --git a/src/ringdht/ringaccount.cpp b/src/ringdht/ringaccount.cpp
index 72362202592fe10e10f9b0ba844ed1e44247e22e..a72a5d0faa1dc5c146a4bdf2f89b425df4127a71 100644
--- a/src/ringdht/ringaccount.cpp
+++ b/src/ringdht/ringaccount.cpp
@@ -325,7 +325,7 @@ RingAccount::startOutgoingCall(std::shared_ptr<SIPCall>& call, const std::string
                     const dht::Value::Id callvid  = udist(sthis->rand_);
                     const dht::Value::Id vid  = udist(sthis->rand_);
                     const auto callkey = dht::InfoHash::get("callto:" + dev.dev.toString());
-                    dht::Value val { dht::IceCandidates(callvid, ice->getLocalAttributesAndCandidates()) };
+                    dht::Value val { dht::IceCandidates(callvid, ice->packIceMsg()) };
                     val.id = vid;
 
                     sthis->dht_.putEncrypted(
@@ -1842,7 +1842,7 @@ RingAccount::replyToIncomingIceMsg(std::shared_ptr<SIPCall> call,
     registerDhtAddress(*ice);
 
     const auto vid = udist(rand_);
-    dht::Value val { dht::IceCandidates(peer_ice_msg.id, ice->getLocalAttributesAndCandidates()) };
+    dht::Value val { dht::IceCandidates(peer_ice_msg.id, ice->packIceMsg()) };
     val.id = vid;
 
     std::weak_ptr<SIPCall> wcall = call;