diff --git a/bsd/sys/net/if_loop.cc b/bsd/sys/net/if_loop.cc
index 5697545bbd7a31e6d5db742a0f1292e0b7c8a128..09ffe899a3c4570109c15a3dd79857f8f7d883d1 100644
--- a/bsd/sys/net/if_loop.cc
+++ b/bsd/sys/net/if_loop.cc
@@ -35,6 +35,7 @@
  */
 
 #include <osv/ioctl.h>
+#include <osv/net_trace.hh>
 
 #include <bsd/porting/netport.h>
 #include <bsd/sys/sys/param.h>
@@ -287,6 +288,7 @@ if_simloop(struct ifnet *ifp, struct mbuf *m, int af, int hlen)
 	}
 	ifp->if_ipackets++;
 	ifp->if_ibytes += m->M_dat.MH.MH_pkthdr.len;
+	log_loopback_packet(m);
 	netisr_queue(isr, m);	/* mbuf is free'd on failure. */
 	return (0);
 }
diff --git a/build.mk b/build.mk
index c1d77d0965bd1699b47e6e14ff8af4457855a885..c88c6eea006c9751264f14b5ac5c1426ec876352 100644
--- a/build.mk
+++ b/build.mk
@@ -750,6 +750,7 @@ objects += core/chart.o
 objects += core/net_channel.o
 objects += core/demangle.o
 objects += core/async.o
+objects += core/net_trace.o
 
 include $(src)/fs/build.mk
 include $(src)/libc/build.mk
diff --git a/core/net_trace.cc b/core/net_trace.cc
new file mode 100644
index 0000000000000000000000000000000000000000..32d4961c18cdbb75fad09773c6a978a593ba9068
--- /dev/null
+++ b/core/net_trace.cc
@@ -0,0 +1,148 @@
+/*
+ * 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 <osv/trace.hh>
+#include <osv/net_trace.hh>
+#include <vector>
+#include <iterator>
+
+class mbuf_iterator : public std::iterator<std::input_iterator_tag, char, size_t> {
+private:
+    struct mbuf* _m;
+    size_t _pos;
+private:
+    void ensure_next()
+    {
+        while (_m != nullptr && _pos == (size_t) _m->m_hdr.mh_len) {
+            _m = _m->m_hdr.mh_next;
+            _pos = 0;
+        }
+    }
+public:
+    mbuf_iterator(struct mbuf* m)
+        : _m(m)
+        , _pos(0)
+    {
+        ensure_next();
+    }
+
+    void operator++()
+    {
+        _pos++;
+        ensure_next();
+    }
+
+    mbuf_iterator operator+(size_t delta) const
+    {
+        mbuf_iterator new_iterator(*this);
+        new_iterator += delta;
+        return new_iterator;
+    }
+
+    void operator+=(size_t delta)
+    {
+        while (_m != nullptr && delta > 0) {
+            auto step = std::min(delta, _m->m_hdr.mh_len - _pos);
+            _pos += step;
+            delta -= step;
+            ensure_next();
+        }
+    }
+
+    char& operator*()
+    {
+        return _m->m_hdr.mh_data[_pos];
+    }
+
+    char* operator->()
+    {
+        return &_m->m_hdr.mh_data[_pos];
+    }
+
+    bool operator==(const mbuf_iterator& other) const
+    {
+        return other._m == _m && other._pos == _pos;
+    }
+
+    bool operator!=(const mbuf_iterator& other) const
+    {
+        return !(*this == other);
+    }
+
+    friend size_t std::distance<mbuf_iterator>(mbuf_iterator, mbuf_iterator);
+};
+
+namespace std {
+
+template<>
+size_t distance<mbuf_iterator>(mbuf_iterator first, mbuf_iterator second)
+{
+    if (first._m == second._m) {
+        return second._pos - first._pos;
+    }
+
+    size_t distance = 0;
+    auto* m = first._m;
+
+    if (m) {
+        distance += m->m_hdr.mh_len - first._pos;
+    }
+
+    m = m->m_hdr.mh_next;
+
+    while (m && m != second._m) {
+        distance += m->m_hdr.mh_len;
+        m = m->m_hdr.mh_next;
+    }
+
+    if (m) {
+        distance += second._pos;
+    }
+
+    return distance;
+}
+
+}
+
+template<size_t limit>
+class mbuf_slice : public blob_tag {
+private:
+    struct mbuf* _m;
+public:
+    using iterator = mbuf_iterator;
+
+    mbuf_slice(struct mbuf* m)
+        : _m(m)
+    {
+    }
+
+    iterator begin() const
+    {
+        return mbuf_iterator(_m);
+    }
+
+    iterator end() const
+    {
+        return begin() + limit;
+    }
+};
+
+static constexpr size_t capture_limit = 128;
+using slice_t = mbuf_slice<capture_limit>;
+
+TRACEPOINT(trace_net_packet_eth, "if=%d, data=%s", int, slice_t);
+TRACEPOINT(trace_net_packet_loopback, "data=%s", slice_t);
+
+void log_eth_packet(struct ifnet *ifp, struct mbuf* m)
+{
+    trace_net_packet_eth(ifp->if_index, slice_t(m));
+}
+
+void log_loopback_packet(struct mbuf* m)
+{
+    trace_net_packet_loopback(slice_t(m));
+}
diff --git a/drivers/virtio-net.cc b/drivers/virtio-net.cc
index 3fe369300d800478b637f193b41fe6490d900c82..790a721fc36a363ecacaee761c97bcc46bad7ac5 100644
--- a/drivers/virtio-net.cc
+++ b/drivers/virtio-net.cc
@@ -23,8 +23,8 @@
 #include <osv/debug.h>
 
 #include <osv/sched.hh>
-#include "osv/trace.hh"
-
+#include <osv/trace.hh>
+#include <osv/net_trace.hh>
 
 #include <osv/device.h>
 #include <osv/ioctl.h>
@@ -121,6 +121,8 @@ static int if_transmit(struct ifnet* ifp, struct mbuf* m_head)
 {
     net* vnet = (net*)ifp->if_softc;
 
+    log_eth_packet(ifp, m_head);
+
     net_d("%s_start", __FUNCTION__);
 
     /* Process packets */
@@ -447,6 +449,8 @@ void net::receiver()
             rx_packets++;
             rx_bytes += m_head->M_dat.MH.MH_pkthdr.len;
 
+            log_eth_packet(_ifn, m_head);
+
             bool fast_path = _ifn->if_classifier.post_packet(m_head);
             if (!fast_path) {
                 (*_ifn->if_input)(_ifn, m_head);
diff --git a/include/osv/net_trace.hh b/include/osv/net_trace.hh
new file mode 100644
index 0000000000000000000000000000000000000000..844010caa56eb995e02e9b580f43aada30c5a62e
--- /dev/null
+++ b/include/osv/net_trace.hh
@@ -0,0 +1,17 @@
+/*
+ * 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 NET_TRACE_HH
+#define NET_TRACE_HH
+
+#include <bsd/sys/sys/mbuf.h>
+#include <bsd/sys/net/if.h>
+
+void log_eth_packet(struct ifnet *ifp, struct mbuf *m);
+void log_loopback_packet(struct mbuf *m);
+
+#endif