diff --git a/sim/scenarios/asymmetric-p2p/asymmetric-p2p.cc b/sim/scenarios/asymmetric-p2p/asymmetric-p2p.cc
index 19e9cd6d4f920560706ff4cd0e73a371b4d27d12..6ce1a6166db74c64f167132e12f58ae24938480f 100644
--- a/sim/scenarios/asymmetric-p2p/asymmetric-p2p.cc
+++ b/sim/scenarios/asymmetric-p2p/asymmetric-p2p.cc
@@ -8,12 +8,51 @@
 #include "../helper/quic-network-simulator-helper.h"
 #include "../helper/quic-point-to-point-helper.h"
 #include "drop-rate-error-model.h"
+#include "gilbert-elliott-drop-model.h"
+#include <signal.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/time.h>
+
 
 using namespace ns3;
 using namespace std;
 
 NS_LOG_COMPONENT_DEFINE("ns3 simulator");
 
+static Ptr<Queue<Packet>> returnQueue;
+static Ptr<Queue<Packet>> forwardQueue;
+static constexpr unsigned int QUEUE_LOG_INTERVAL_USEC = 200000;
+
+void timer_handler(int signum){
+    cout <<
+        "# return\t" <<
+        returnQueue->GetNPackets() << "pkgs \t" <<
+        returnQueue->GetNBytes() << "bytes \t" <<
+        returnQueue->GetTotalReceivedPackets() << " pkgs\t" <<
+        returnQueue->GetTotalReceivedBytes() << " bytes\t" <<
+        returnQueue->GetTotalDroppedPackets() << " pkgs\t" <<
+        returnQueue->GetTotalDroppedBytes() << " bytes\t" <<
+        returnQueue->GetTotalDroppedPacketsBeforeEnqueue() << " pkgs\t" <<
+        returnQueue->GetTotalDroppedBytesBeforeEnqueue() << " bytes\t" <<
+        returnQueue->GetTotalDroppedPacketsAfterDequeue() << " pkgs\t" <<
+        returnQueue->GetTotalDroppedBytesAfterDequeue() << " bytes" <<
+        endl;
+    cout <<
+        "# forward\t" <<
+        forwardQueue->GetNPackets() << "pkgs \t" <<
+        forwardQueue->GetNBytes() << "bytes \t" <<
+        forwardQueue->GetTotalReceivedPackets() << " pkgs\t" <<
+        forwardQueue->GetTotalReceivedBytes() << " bytes\t" <<
+        forwardQueue->GetTotalDroppedPackets() << " pkgs\t" <<
+        forwardQueue->GetTotalDroppedBytes() << " bytes\t" <<
+        forwardQueue->GetTotalDroppedPacketsBeforeEnqueue() << " pkgs\t" <<
+        forwardQueue->GetTotalDroppedBytesBeforeEnqueue() << " bytes\t" <<
+        forwardQueue->GetTotalDroppedPacketsAfterDequeue() << " pkgs\t" <<
+        forwardQueue->GetTotalDroppedBytesAfterDequeue() << " bytes" <<
+        endl;
+}
+
 int main(int argc, char *argv[]) {
 
     string channelDelay;
@@ -21,14 +60,17 @@ int main(int argc, char *argv[]) {
     string forwardQueueSize;
     string returnDataRate;
     string returnQueueSize;
-    string drop_rate_to_client;
-    string drop_rate_to_server;
+    string goodBadProbToClient;
+    string badGoodProbToClient;
+    string goodBadProbToServer;
+    string badGoodProbToServer;
+    string dropRateToClient;
+    string dropRateToServer;
+    bool linkErrorModels = false;
 
     // for dropping packets:
     random_device rand_dev;
     mt19937 generator(rand_dev());  // Seed random number generator first
-    Ptr<DropRateErrorModel> drops_to_client = CreateObject<DropRateErrorModel>();
-    Ptr<DropRateErrorModel> drops_to_server = CreateObject<DropRateErrorModel>();
 
     CommandLine cmd;
     cmd.AddValue("delay", "delay of the channel in both directions (RTT = 2 * delay)", channelDelay);
@@ -36,8 +78,13 @@ int main(int argc, char *argv[]) {
     cmd.AddValue("forward-queue", "queue size of the forward link (right -> left) (in packets)", forwardQueueSize);
     cmd.AddValue("return-data-rate", "data rate of the return link (left -> right)", returnDataRate);
     cmd.AddValue("return-queue", "queue size of the return link (left -> right) (in packets)", returnQueueSize);
-    cmd.AddValue("drop-rate-to-client", "packet drop rate (towards client)", drop_rate_to_client);
-    cmd.AddValue("drop-rate-to-server", "packet drop rate (towards server)", drop_rate_to_server);
+    cmd.AddValue("good-bad-prob-to-client", "Gilber-Elliott Probability for Transition from Good to Bad (towards client)", goodBadProbToClient);
+    cmd.AddValue("bad-good-prob-to-client", "Gilber-Elliott Probability for Transition from Bad to Good (towards client)", badGoodProbToClient);
+    cmd.AddValue("good-bad-prob-to-server", "Gilber-Elliott Probability for Transition from Good to Bad (towards server)", goodBadProbToServer);
+    cmd.AddValue("bad-good-prob-to-server", "Gilber-Elliott Probability for Transition from Bad to Good (towards server)", badGoodProbToServer);
+    cmd.AddValue("link-error-models", "Use the same error model instance in both directions (-> produce errors during the same time)", linkErrorModels);
+    cmd.AddValue("drop-rate-to-client", "random packet drop rate (towards client)", dropRateToClient);
+    cmd.AddValue("drop-rate-to-server", "random packet drop rate (towards server)", dropRateToServer);
     cmd.Parse(argc, argv);
 
     NS_ABORT_MSG_IF(channelDelay.length() == 0, "Missing parameter: delay");
@@ -59,27 +106,87 @@ int main(int argc, char *argv[]) {
     leftP2PNetDev->SetDataRate(DataRate(returnDataRate));
     rightP2PNetDev->SetDataRate(DataRate(forwardDataRate));
 
-    auto returnQueue = leftP2PNetDev->GetQueue();
-    auto forwardQueue = rightP2PNetDev->GetQueue();
+    returnQueue = leftP2PNetDev->GetQueue();
+    forwardQueue = rightP2PNetDev->GetQueue();
     returnQueue->SetMaxSize(QueueSize(returnQueueSize + "p"));
     forwardQueue->SetMaxSize(QueueSize(forwardQueueSize + "p"));
 
+    struct sigaction sa;
+    struct itimerval timer;
+
+    /* Install timer_handler as the signal handler for SIGVTALRM. */
+    memset(&sa, 0, sizeof(sa));
+    sa.sa_handler = &timer_handler;
+    sigaction(SIGVTALRM, &sa, NULL);
+    cout << "# queue\t" <<
+        "size pkgs\tsize bytes\t" <<
+        "rx pkgs\trx bytes\t" <<
+        "dropped pkgs\tdropped bytes\t" <<
+        "dr b enq pkgs\tdr b enq bytes\t" <<
+        "dr a deq pkgs\tdr a deq pkgs bytes" <<
+        endl;
+
+    /* Configure the timer to expire after x usec... */
+    timer.it_value.tv_sec = 0;
+    timer.it_value.tv_usec = QUEUE_LOG_INTERVAL_USEC;
+    /* ... and every x usec after that. */
+    timer.it_interval.tv_sec = 0;
+    timer.it_interval.tv_usec = QUEUE_LOG_INTERVAL_USEC;
+    /* Start a virtual timer. It counts down whenever this process is executing. */
+    setitimer(ITIMER_VIRTUAL, &timer, NULL);
+
     // Set client and server drop rates.
-    if (drop_rate_to_client.length() == 0) {
-        drops_to_client->SetDropRate(0);
-        cout << "Using drop rate to client: 0 %. (Use --drop-rate-to-client to change)" << endl;
+    if (linkErrorModels && (goodBadProbToClient.length() == 0 || badGoodProbToClient.length() == 0 || dropRateToClient.length() != 0 || goodBadProbToServer.length() != 0 || badGoodProbToServer.length() != 0 || dropRateToServer.length() != 0)) {
+        NS_ABORT_MSG_IF(true, "--link-error-models must be used with --good-bad-prob-to-client and --bad-good-prob-to-client only.");
+    } else if (linkErrorModels && goodBadProbToClient.length() != 0 && badGoodProbToClient.length() != 0) {
+        // link error models
+        auto probGB = stof(goodBadProbToClient);
+        auto probBG = stof(badGoodProbToClient);
+        cout << "Using Gilbert-Elliot-Loss-Model for both directions with P(b|g) = " << probGB << " \% and P(g|b) = " << probBG << " \%" << endl;
+        Ptr<GilbertElliottDropModel> drops = CreateObject<GilbertElliottDropModel>();
+        drops->SetProbabilities(probGB, probBG);
+        devices.Get(0)->SetAttribute("ReceiveErrorModel", PointerValue(drops));
+        devices.Get(1)->SetAttribute("ReceiveErrorModel", PointerValue(drops));
     } else {
-        drops_to_client->SetDropRate(stoi(drop_rate_to_client));
-    }
-    if (drop_rate_to_server.length() == 0) {
-        drops_to_server->SetDropRate(0);
-        cout << "Using drop rate to server: 0 %. (Use --drop-rate-to-server to change)" << endl;
-    } else {
-        drops_to_server->SetDropRate(stoi(drop_rate_to_server));
-    }
 
-    devices.Get(0)->SetAttribute("ReceiveErrorModel", PointerValue(drops_to_client));
-    devices.Get(1)->SetAttribute("ReceiveErrorModel", PointerValue(drops_to_server));
+        if (goodBadProbToClient.length() > 0 && badGoodProbToClient.length() > 0 && dropRateToClient.length() == 0) {
+            auto probGB = stof(goodBadProbToClient);
+            auto probBG = stof(badGoodProbToClient);
+            cout << "Using Gilbert-Elliot-Loss-Model for forward path with P(b|g) = " << probGB << " \% and P(g|b) = " << probBG << " \%" << endl;
+            Ptr<GilbertElliottDropModel> dropsToClient = CreateObject<GilbertElliottDropModel>();
+            dropsToClient->SetProbabilities(probGB, probBG);
+            devices.Get(0)->SetAttribute("ReceiveErrorModel", PointerValue(dropsToClient));
+        } else if (goodBadProbToClient.length() == 0 && badGoodProbToClient.length() == 0 && dropRateToClient.length() > 0) {
+            cout << "Using uniform random loss model in forward path with P('err') = " << dropRateToClient << " \%" << endl;
+            Ptr<DropRateErrorModel> dropsToClient = CreateObject<DropRateErrorModel>();
+            dropsToClient->SetDropRate(stoi(dropRateToClient));
+            devices.Get(0)->SetAttribute("ReceiveErrorModel", PointerValue(dropsToClient));
+        } else if (goodBadProbToClient.length() == 0 && badGoodProbToClient.length() == 0 && dropRateToClient.length() == 0) {
+            cout << "Using drop rate to client: 0 %. (Use --drop-rate-to-client or --good-bad-prob-to-client and --bad-good-prob-to-client to change)" << endl;
+        } else {
+            cerr << "Cannot mix random loss model (--drop-rate-to-client) and Gilbert-Elliott-Loss-Model (--good-bad-prob-to-client and --bad-good-prob-to-client)" << endl;
+            NS_ABORT_MSG_IF(true, "Fix arguments!");
+        }
+
+        if (goodBadProbToServer.length() > 0 && badGoodProbToServer.length() > 0 && dropRateToServer.length() == 0) {
+            auto probGB = stof(goodBadProbToServer);
+            auto probBG = stof(badGoodProbToServer);
+            cout << "Using Gilbert-Elliot-Loss-Model for return path with P(b|g) = " << probGB << " \% and P(g|b) = " << probBG << " \%" << endl;
+            Ptr<GilbertElliottDropModel> dropsToServer = CreateObject<GilbertElliottDropModel>();
+            dropsToServer->SetProbabilities(probGB, probBG);
+            devices.Get(1)->SetAttribute("ReceiveErrorModel", PointerValue(dropsToServer));
+        } else if (goodBadProbToServer.length() == 0 && badGoodProbToServer.length() == 0 && dropRateToServer.length() > 0) {
+            cout << "Using uniform random loss model in return path with P('err') = " << dropRateToServer << " \%" << endl;
+            Ptr<DropRateErrorModel> dropsToServer = CreateObject<DropRateErrorModel>();
+            dropsToServer->SetDropRate(stoi(dropRateToServer));
+            devices.Get(1)->SetAttribute("ReceiveErrorModel", PointerValue(dropsToServer));
+        } else if (goodBadProbToServer.length() == 0 && badGoodProbToServer.length() == 0 && dropRateToServer.length() == 0) {
+            cout << "Using drop rate to server: 0 %. (Use --drop-rate-to-server or --good-bad-prob-to-server and --bad-good-prob-to-server to change)" << endl;
+        } else {
+            cerr << "Cannot mix random loss model (--drop-rate-to-server) and Gilbert-Elliott-Loss-Model (--good-bad-prob-to-server and --bad-good-prob-to-server)" << endl;
+            NS_ABORT_MSG_IF(true, "Fix arguments!");
+        }
+    }
 
     sim.Run(Seconds(36000));
 }
diff --git a/sim/scenarios/asymmetric-p2p/gilbert-elliott-drop-model.cc b/sim/scenarios/asymmetric-p2p/gilbert-elliott-drop-model.cc
new file mode 100644
index 0000000000000000000000000000000000000000..23e2fe9dd8309d262e8aea1022b74886f09ca33b
--- /dev/null
+++ b/sim/scenarios/asymmetric-p2p/gilbert-elliott-drop-model.cc
@@ -0,0 +1,86 @@
+#include "../helper/quic-packet.h"
+#include "gilbert-elliott-drop-model.h"
+
+using namespace std;
+
+NS_OBJECT_ENSURE_REGISTERED(GilbertElliottDropModel);
+
+TypeId GilbertElliottDropModel::GetTypeId(void) {
+    static TypeId tid = TypeId("GilbertElliottDropModel")
+        .SetParent<ErrorModel>()
+        .AddConstructor<GilbertElliottDropModel>()
+        ;
+    return tid;
+}
+
+GilbertElliottDropModel::GilbertElliottDropModel()
+    :
+        goodBadPerc(0),
+        badGoodPerc(0),
+        state(GilbertElliottState::GOOD),
+        distr(0.0, 1.0),
+        drop_counter(0),
+        pass_counter(0),
+        first_packet_logged(false)
+{
+    std::random_device rd;
+    rng = new std::mt19937(rd());
+}
+
+void GilbertElliottDropModel::DoReset(void) { }
+
+void GilbertElliottDropModel::log(void) {
+    if (!first_packet_logged) {
+        if (pass_counter > 0) {
+            cout << "First packet forwarded." << endl;
+        } else if (drop_counter > 0) {
+            cout << "First packet dropped." << endl;
+        } else {
+            cout << "WTF!" << endl;
+        }
+        first_packet_logged = true;
+        cout << "Will log stats after every " << LOG_AFTER_PACKETS << "th packet." << endl;
+    } else if (drop_counter + pass_counter >= LOG_AFTER_PACKETS) {
+        cout << "Among the last " <<
+            LOG_AFTER_PACKETS << " packets, " <<
+            drop_counter << " packets were dropped and " <<
+            pass_counter << " were forwarded -> p_err = " <<
+            drop_counter * 100.0 / LOG_AFTER_PACKETS << " %" <<
+            endl;
+        drop_counter = 0;
+        pass_counter = 0;
+    }
+}
+
+bool GilbertElliottDropModel::DoCorrupt(Ptr<Packet> p) {
+    if (!IsUDPPacket(p)) {
+        return false;
+    }
+
+    QuicPacket qp = QuicPacket(p);
+
+    // check if we switch the state:
+    float rnd = distr(*rng);
+    if (this->state == GilbertElliottState::GOOD && rnd < this->goodBadPerc) {
+        state = GilbertElliottState::BAD;
+    } else if (this->state == GilbertElliottState::BAD && rnd < this->badGoodPerc) {
+        state = GilbertElliottState::GOOD;
+    }
+
+    if (this->state == GilbertElliottState::GOOD) {
+        pass_counter += 1;
+        log();
+        qp.ReassemblePacket();
+        return false;
+    } else {
+        drop_counter += 1;
+        log();
+        return true;
+    }
+
+}
+
+void GilbertElliottDropModel::SetProbabilities(float goodBadPerc, float badGoodPerc) {
+    this->goodBadPerc = goodBadPerc;
+    this->badGoodPerc = badGoodPerc;
+}
diff --git a/sim/scenarios/asymmetric-p2p/gilbert-elliott-drop-model.h b/sim/scenarios/asymmetric-p2p/gilbert-elliott-drop-model.h
new file mode 100644
index 0000000000000000000000000000000000000000..b991d141c1af91cef5c2180648fec02c1e7f3b24
--- /dev/null
+++ b/sim/scenarios/asymmetric-p2p/gilbert-elliott-drop-model.h
@@ -0,0 +1,42 @@
+#ifndef GILBERT_ELLIOTT_DROP_MODEL_H
+#define GILBERT_ELLIOTT_DROP_MODEL_H
+
+#include <set>
+#include <random>
+#include "ns3/error-model.h"
+
+using namespace ns3;
+
+enum GilbertElliottState {
+  GOOD,
+  BAD,
+};
+
+// The model drops packets in the error state and passes packets in the normal state.
+// The transition between both states is described by the transition probabilities.
+class GilbertElliottDropModel : public ErrorModel {
+ public:
+    static TypeId GetTypeId(void);
+    GilbertElliottDropModel();
+    void SetProbabilities(float goodBadPerc, float badGoodPerc);
+
+ private:
+    float goodBadPerc;
+    float badGoodPerc;
+    GilbertElliottState state;
+
+    std::mt19937 *rng;
+    std::uniform_real_distribution<> distr;
+
+    unsigned int drop_counter;
+    unsigned int pass_counter;
+    bool first_packet_logged;
+    static constexpr unsigned int LOG_AFTER_PACKETS = 1000;
+
+    bool DoCorrupt(Ptr<Packet> p);
+    void DoReset(void);
+
+    void log(void);
+};
+
+#endif /* GILBERT_ELLIOTT_DROP_MODEL_H */
diff --git a/sim/scenarios/drop-rate/drop-rate-error-model.cc b/sim/scenarios/drop-rate/drop-rate-error-model.cc
index 4234e4d15ab7d4b76c0be5bf83a06c92e5f268b3..45982efe3a9c1d9108fd93c640799965dfe81e7d 100644
--- a/sim/scenarios/drop-rate/drop-rate-error-model.cc
+++ b/sim/scenarios/drop-rate/drop-rate-error-model.cc
@@ -39,7 +39,12 @@ void DropRateErrorModel::log(void) {
         first_packet_logged = true;
         cout << "Will log stats after every " << LOG_AFTER_PACKETS << "th packet." << endl;
     } else if (drop_counter + pass_counter >= LOG_AFTER_PACKETS) {
-        cout << "Among the last " << LOG_AFTER_PACKETS << " packets, " << drop_counter << " packets were dropped and " << pass_counter << " were forwarded." << endl;
+        cout << "Among the last " <<
+            LOG_AFTER_PACKETS << " packets, " <<
+            drop_counter << " packets were dropped and " <<
+            pass_counter << " were forwarded -> p_err = " <<
+            drop_counter * 100.0 / LOG_AFTER_PACKETS << " %" <<
+            endl;
         drop_counter = 0;
         pass_counter = 0;
     }
diff --git a/sim/scenarios/drop-rate/drop-rate-error-model.h b/sim/scenarios/drop-rate/drop-rate-error-model.h
index 74a8fbf8bb0e5fbcde80dd66155bf948dba01a42..07208245e596462cee9ce12a163f97da7392ec22 100644
--- a/sim/scenarios/drop-rate/drop-rate-error-model.h
+++ b/sim/scenarios/drop-rate/drop-rate-error-model.h
@@ -24,7 +24,7 @@ class DropRateErrorModel : public ErrorModel {
     bool first_packet_logged;
     static constexpr unsigned int LOG_AFTER_PACKETS = 1000;
 
-    bool DoCorrupt (Ptr<Packet> p);
+    bool DoCorrupt(Ptr<Packet> p);
     void DoReset(void);
 
     void log(void);