Example #1
0
  void IP4::transmit(Packet_ptr pckt) {
    assert(pckt->size() > sizeof(IP4::full_header));

    auto ip4_pckt = std::static_pointer_cast<PacketIP4>(pckt);
    ip4_pckt->make_flight_ready();

    IP4::ip_header& hdr = ip4_pckt->ip4_header();
    // Create local and target subnets
    addr target = hdr.daddr        & stack_.netmask();
    addr local  = stack_.ip_addr() & stack_.netmask();
    
    // Compare subnets to know where to send packet
    pckt->next_hop(target == local ? hdr.daddr : stack_.router());

    debug("<IP4 TOP> Next hop for %s, (netmask %s, local IP: %s, gateway: %s) == %s\n",
          hdr.daddr.str().c_str(),
          stack_.netmask().str().c_str(),
          stack_.ip_addr().str().c_str(),
          stack_.router().str().c_str(),
          target == local ? "DIRECT" : "GATEWAY");

    debug("<IP4 transmit> my ip: %s, Next hop: %s, Packet size: %i IP4-size: %i\n",
          stack_.ip_addr().str().c_str(),
          pckt->next_hop().str().c_str(),
          pckt->size(),
          ip4_pckt->ip4_segment_size()
          );

    linklayer_out_(pckt);
  }
Example #2
0
  void Ethernet::receive(Packet_ptr pckt) {
    Expects(pckt->size() > 0);

    header* eth = reinterpret_cast<header*>(pckt->buffer());

    /** Do we pass on ethernet headers? As for now, yes.
        data += sizeof(header);
        len -= sizeof(header);
    */
    debug2("<Ethernet IN> %s => %s , Eth.type: 0x%x ",
           eth->src.str().c_str(), eth->dest.str().c_str(), eth->type);

    // Stat increment packets received
    packets_rx_++;

    bool dropped = false;

    switch(eth->type) {
    case ETH_IP4:
      debug2("IPv4 packet\n");
      ip4_upstream_(std::move(pckt));
      break;

    case ETH_IP6:
      debug2("IPv6 packet\n");
      ip6_upstream_(std::move(pckt));
      break;

    case ETH_ARP:
      debug2("ARP packet\n");
      arp_upstream_(std::move(pckt));
      break;

    case ETH_WOL:
      dropped = true;
      debug2("Wake-on-LAN packet\n");
      break;

    case ETH_VLAN:
      dropped = true;
      debug("VLAN tagged frame (not yet supported)");
      break;

    default:
      dropped = true;
      // This might be 802.3 LLC traffic
      if (net::ntohs(eth->type) > 1500) {
        debug2("<Ethernet> UNKNOWN ethertype 0x%x\n", ntohs(eth->type));
      }else {
        debug2("IEEE802.3 Length field: 0x%x\n", ntohs(eth->type));
      }
      break;
    }

    if(dropped)
      packets_dropped_++;
  }
Example #3
0
void Listener::segment_arrived(Packet_ptr packet) {
  debug2("<Listener::segment_arrived> Received packet: %s\n",
    packet->to_string().c_str());
  // if it's an reply to any of our half-open connections
  if(!packet->isset(SYN))
  {
    // if there is a connection waiting for the packet
    for(auto conn : syn_queue_) // take a copy to avoid conn->segment_arrived to make a reference invalid
    {
      if(conn->remote() == packet->source())
      {
        debug("<Listener::segment_arrived> Found packet receiver: %s\n",
          conn->to_string().c_str());
        conn->segment_arrived(packet);
        debug2("<Listener::segment_arrived> Connection done handling segment\n");
        return;
      }
    }
  }
  // if it's a new attempt (SYN)
  else
  {
    // Stat increment number of connection attempts
    host_.connection_attempts_++;

    // if we don't like this client, do nothing
    if(! on_accept_(packet->source()) )
      return;

    // remove oldest connection if queue is full
    debug2("<Listener::segment_arrived> SynQueue: %u\n", syn_queue_.size());
    if(syn_queue_full())
    {
      debug2("<Listener::segment_arrived> Queue is full\n");
      Expects(not syn_queue_.empty());
      debug("<Listener::segment_arrived> Connection %s dropped to make room for new connection\n",
        syn_queue_.back()->to_string().c_str());

      syn_queue_.pop_back();
    }

    auto& conn = *(syn_queue_.emplace(syn_queue_.begin(),
      std::make_shared<Connection>( host_, port_, packet->source() )));
    // Call Listener::connected when Connection is connected
    conn->on_connect(ConnectCallback::from<Listener, &Listener::connected>(this));
    conn->_on_cleanup(CleanupCallback::from<Listener, &Listener::remove>(this));
    // Open connection
    conn->open(false);
    Ensures(conn->is_listening());
    debug("<Listener::segment_arrived> Connection %s created\n",
      conn->to_string().c_str());
    conn->segment_arrived(packet);
    debug2("<Listener::segment_arrived> Connection done handling segment\n");
    return;
  }
  debug2("<Listener::segment_arrived> No receipent\n");
}
Example #4
0
  void Arp::receive(Packet_ptr pckt) {
    PRINT("<ARP handler> got %i bytes of data\n", pckt->size());

    header* hdr = reinterpret_cast<header*>(pckt->layer_begin());

    /// cache entry
    this->cache(hdr->sipaddr, hdr->shwaddr);

    /// always try to ship waiting packets when someone talks
    auto waiting = waiting_packets_.find(hdr->sipaddr);
    if (waiting != waiting_packets_.end()) {
      PRINT("<Arp> Had a packet waiting for this IP. Sending\n");
      transmit(std::move(waiting->second.pckt), hdr->sipaddr);
      waiting_packets_.erase(waiting);
    }

    switch(hdr->opcode) {
    case H_request: {
      // Stat increment requests received
      requests_rx_++;

      PRINT("<Arp> %s is looking for %s\n",
             hdr->sipaddr.str().c_str(),
             hdr->dipaddr.str().c_str());

      if (hdr->dipaddr == inet_.ip_addr()) {

        // The packet is for us. Respond.
        arp_respond(hdr, inet_.ip_addr());

      } else if (proxy_ and proxy_(hdr->dipaddr)){

        // The packet is for an IP to which we know a route
        arp_respond(hdr, hdr->dipaddr);

      } else {

        // Drop
        PRINT("\t NO MATCH for My IP (%s). DROP!\n",
               inet_.ip_addr().str().c_str());
      }
      break;
    }
    case H_reply: {
      // Stat increment replies received
      replies_rx_++;
      PRINT("\t ARP REPLY: %s belongs to %s (waiting: %u)\n",
             hdr->sipaddr.str().c_str(), hdr->shwaddr.str().c_str(), waiting_packets_.size());
      break;
    }
    default:
      PRINT("\t UNKNOWN OPCODE\n");
      break;
    } //< switch(hdr->opcode)

  }
Example #5
0
 /* Add a packet to this packet chain.  */
 void chain(Packet_ptr p) noexcept {
     if (!chain_) {
         chain_ = p;
         last_ = p;
     } else {
         last_->chain(p);
         last_ = p->last_in_chain() ? p->last_in_chain() : p;
         assert(last_);
     }
 }
Example #6
0
void Arp::await_resolution(Packet_ptr pckt, IP4::addr){
  auto queue =  waiting_packets_.find(pckt->next_hop());
  if (queue != waiting_packets_.end()) {
    debug("<ARP Resolve> Packets allready queueing for this IP\n");
    queue->second->chain(pckt);    
  } else {
    debug("<ARP Resolve> This is the first packet going to that IP\n");
    waiting_packets_.emplace(std::make_pair(pckt->next_hop(), pckt));
  }            
}
Example #7
0
int Arp::transmit(Packet_ptr pckt){
  
  assert(pckt->size());
  
  /** Get destination IP from IP header   */
  IP4::ip_header* iphdr = (IP4::ip_header*)(pckt->buffer() 
                                            + sizeof(Ethernet::header));
  IP4::addr sip = iphdr->saddr;
  IP4::addr dip = pckt->next_hop();

  debug2("<ARP -> physical> Transmitting %i bytes to %s \n",
        pckt->size(),dip.str().c_str());
  
  Ethernet::addr mac;
  
  if (iphdr->daddr == IP4::INADDR_BCAST)
  {
    // when broadcasting our source IP should be either
    // our own IP or 0.0.0.0
    static const IP4::addr INADDR_NONE  {{0}};
    if (sip != ip_ && sip != INADDR_NONE)
    {
      debug2("<ARP> Dropping outbound broadcast packet due to "
             "invalid source IP %s\n",  sip.str().c_str());
      return -1;
    }
    mac = Ethernet::addr::BROADCAST_FRAME;
  }
  else
  {
    if (sip != ip_)
    {
      debug2("<ARP -> physical> Not bound to source IP %s. My IP is %s. DROP!\n",
            sip.str().c_str(), ip_.str().c_str());
      return -1;
    }
    
    // If we don't have a cached IP, get mac from next-hop (HĂ„reks c001 hack)
    if (!is_valid_cached(dip))  
        return arp_resolver_(pckt);
    
    // Get mac from cache
    mac = cache_[dip].mac_;
  }
  
  /** Attach next-hop mac and ethertype to ethernet header  */  
  Ethernet::header* ethhdr = (Ethernet::header*)pckt->buffer();    
  ethhdr->src = mac_;
  ethhdr->dest.major = mac.major;
  ethhdr->dest.minor = mac.minor;
  ethhdr->type = Ethernet::ETH_IP4;
  
  debug2("<ARP -> physical> Sending packet to %s \n",mac.str().c_str());
  return linklayer_out_(pckt);
}
size_t Connection::fill_packet(Packet_ptr packet, const char* buffer, size_t n, Seq seq) {
  Expects(!packet->has_tcp_data());

  auto written = packet->fill(buffer, std::min(n, (size_t)SMSS()));

  packet->set_seq(seq).set_ack(cb.RCV.NXT).set_flag(ACK);

  Ensures(written <= n);

  return written;
}
Example #9
0
int Arp::bottom(Packet_ptr pckt)
{
  debug2("<ARP handler> got %i bytes of data \n", pckt->size());

  header* hdr = (header*) pckt->buffer();
  //debug2("\t OPCODE: 0x%x \n",hdr->opcode);
  //debug2("Chaching IP %s for %s \n", hdr->sipaddr.str().c_str() , hdr->shwaddr.str().c_str())
  debug2("Have valid cache? %s \n",is_valid_cached(hdr->sipaddr) ? "YES":"NO");
  cache(hdr->sipaddr, hdr->shwaddr);
  
  switch(hdr->opcode){
    
  case H_request:
    debug2("\t ARP REQUEST: ");
    debug2("%s is looking for %s \n",
          hdr->sipaddr.str().c_str(),hdr->dipaddr.str().c_str());
    
    if (hdr->dipaddr == ip_)
      arp_respond(hdr);    
    else
    {
      debug2("\t NO MATCH for My IP (%s). DROP!\n",
          ip().str().c_str());
    }
    break;
    
  case H_reply:
    {
      debug2("\t ARP REPLY: %s belongs to %s\n",
	     hdr->sipaddr.str().c_str(), hdr->shwaddr.str().c_str());
      auto waiting = waiting_packets_.find(hdr->sipaddr);
      if (waiting != waiting_packets_.end()) {
	debug ("Had a packet waiting for this IP. Sending\n");
	transmit(waiting->second);
	waiting_packets_.erase(waiting);
      }
    }
    
    break;
    
  default:
    debug2("\t UNKNOWN OPCODE \n");
    break;
  }
  
  // Free the buffer (We're leaf node for this one's path)
  // @todo Freeing here corrupts the outgoing frame. Why?
  //free(data);
  
  return 0 + 0 * pckt->size(); // yep, it's what you think it is (and what's that?!)
};
Example #10
0
int Ethernet::bottom(Packet_ptr pckt)
{
  assert(pckt->size() > 0);
  
  header* eth = (header*) pckt->buffer();  
  
  /** Do we pass on ethernet headers? As for now, yes.
    data += sizeof(header);
    len -= sizeof(header);
  */    
  debug2("<Ethernet IN> %s => %s , Eth.type: 0x%x ",
         eth->src.str().c_str(),
         eth->dest.str().c_str(),eth->type); 


  switch(eth->type){ 

  case ETH_IP4:
    debug2("IPv4 packet \n");
    return ip4_handler_(pckt);

  case ETH_IP6:
    debug2("IPv6 packet \n");
    return ip6_handler_(pckt);
    
  case ETH_ARP:
    debug2("ARP packet \n");
    return arp_handler_(pckt);
    
  case ETH_WOL:
    debug2("Wake-on-LAN packet \n");
    break;

  case ETH_VLAN:
    debug("VLAN tagged frame (not yet supported)");
    
  default:

    // This might be 802.3 LLC traffic
    if (net::ntohs(eth->type) > 1500){
      debug("<Ethernet> UNKNOWN ethertype 0x%x\n",ntohs(eth->type));
    }else{
      debug2("IEEE802.3 Length field: 0x%x\n",ntohs(eth->type));
    }

    break;
    
  }
  
  return -1;
}
Example #11
0
  void Arp::bottom(Packet_ptr pckt) {
    debug2("<ARP handler> got %i bytes of data\n", pckt->size());

    header* hdr = reinterpret_cast<header*>(pckt->buffer());

    debug2("Have valid cache? %s\n", is_valid_cached(hdr->sipaddr) ? "YES" : "NO");
    cache(hdr->sipaddr, hdr->shwaddr);

    switch(hdr->opcode) {

    case H_request: {
      // Stat increment requests received
      requests_rx_++;

      debug2("\t ARP REQUEST: ");
      debug2("%s is looking for %s\n",
             hdr->sipaddr.str().c_str(),
             hdr->dipaddr.str().c_str());

      if (hdr->dipaddr == inet_.ip_addr()) {
        arp_respond(hdr);
      } else {
        debug2("\t NO MATCH for My IP (%s). DROP!\n",
               inet_.ip_addr().str().c_str());
      }
      break;
    }

    case H_reply: {
      // Stat increment replies received
      replies_rx_++;

      debug2("\t ARP REPLY: %s belongs to %s (waiting: %u)\n",
             hdr->sipaddr.str().c_str(), hdr->shwaddr.str().c_str(), waiting_packets_.size());

      auto waiting = waiting_packets_.find(hdr->sipaddr);

      if (waiting != waiting_packets_.end()) {
        debug("Had a packet waiting for this IP. Sending\n");
        transmit(std::move(waiting->second));
        waiting_packets_.erase(waiting);
      }
      break;
    }

    default:
      debug2("\t UNKNOWN OPCODE\n");
      break;
    } //< switch(hdr->opcode)
  }
Example #12
0
int Arp::arp_resolve(Packet_ptr pckt){
  debug("<ARP RESOLVE> %s \n", pckt->next_hop().str().c_str());
  
  await_resolution(pckt, pckt->next_hop());
  
  auto req = std::static_pointer_cast<PacketArp>(inet_.createPacket(sizeof(header)));
  req->init(mac_, ip_);
  
  req->set_dest_mac(Ethernet::addr::BROADCAST_FRAME);
  req->set_dest_ip(pckt->next_hop());
  req->set_opcode(H_request);
  
  linklayer_out_(req);
  
  return 0;
}
Example #13
0
int Ethernet::transmit(Packet_ptr pckt){
  header* hdr = (header*)pckt->buffer();

  // Verify ethernet header
  assert(hdr->dest.major != 0 || hdr->dest.minor !=0);
  assert(hdr->type != 0);
  
  // Add source address
  hdr->src.major = _mac.major;
  hdr->src.minor = _mac.minor;

  debug2("<Ethernet OUT> Transmitting %i b, from %s -> %s. Type: %i \n",
         pckt->size(),hdr->src.str().c_str(), hdr->dest.str().c_str(),hdr->type);
  
  return physical_out_(pckt);
}
Example #14
0
  void IP4::bottom(Packet_ptr pckt) {
    debug2("<IP4 handler> got the data.\n");

    auto data = pckt->buffer();
    ip_header* hdr = &reinterpret_cast<full_header*>(data)->ip_hdr;

    debug2("\t Source IP: %s Dest.IP: %s\n",
           hdr->saddr.str().c_str(), hdr->daddr.str().c_str());

    switch(hdr->protocol){
    case IP4_ICMP:
      debug2("\t Type: ICMP\n");
      icmp_handler_(pckt);
      break;
    case IP4_UDP:
      debug2("\t Type: UDP\n");
      udp_handler_(pckt);
      break;
    case IP4_TCP:
      tcp_handler_(pckt);
      debug2("\t Type: TCP\n");
      break;
    default:
      debug("\t Type: UNKNOWN %i\n", hdr->protocol);
      break;
    }
  }
Example #15
0
  void Arp::arp_resolve(Packet_ptr pckt) {
    debug("<ARP RESOLVE> %s\n", pckt->next_hop().str().c_str());
    const auto next_hop = pckt->next_hop();
    await_resolution(std::move(pckt), next_hop);

    auto req = static_unique_ptr_cast<PacketArp>(inet_.create_packet(sizeof(header)));
    req->init(mac_, inet_.ip_addr());

    req->set_dest_mac(Ethernet::BROADCAST_FRAME);
    req->set_dest_ip(next_hop);
    req->set_opcode(H_request);

    // Stat increment requests sent
    requests_tx_++;

    linklayer_out_(std::move(req));
  }
Example #16
0
  void ICMPv4::bottom(Packet_ptr pckt) {
    if (pckt->size() < sizeof(full_header)) // Drop if not a full header
      return;

    full_header* full_hdr = reinterpret_cast<full_header*>(pckt->buffer());
    icmp_header* hdr = &full_hdr->icmp_hdr;

#ifdef DEBUG
    auto ip_address = full_hdr->ip_hdr.saddr.str().c_str();
#endif

    switch(hdr->type) {
    case (ICMP_ECHO):
      debug("<ICMP> PING from %s\n", ip_address);
      ping_reply(full_hdr, pckt->size());
      break;
    case (ICMP_ECHO_REPLY):
      debug("<ICMP> PING Reply from %s\n", ip_address);
      break;
    }
  }
Example #17
0
int ICMP::bottom(Packet_ptr pckt){

  if (pckt->size() < sizeof(full_header)) //Drop if not a full header.
    return -1;
  
  full_header* full_hdr = (full_header*)pckt->buffer();
  icmp_header* hdr = &full_hdr->icmp_hdr;
  
  switch(hdr->type)
  {
  case (ICMP_ECHO):
    debug("<ICMP> PING from %s \n",full_hdr->ip_hdr.saddr.str().c_str());
    ping_reply(full_hdr);
    break;
  case (ICMP_ECHO_REPLY):
    debug("<ICMP> PING Reply from %s \n",full_hdr->ip_hdr.saddr.str().c_str());
    break;
  }
  
  return 0;
}
Example #18
0
  void Arp::transmit(Packet_ptr pckt, IP4::addr next_hop) {

    Expects(pckt->size());

    PRINT("<ARP -> physical> Transmitting %u bytes to %s\n",
          (uint32_t) pckt->size(), next_hop.str().c_str());

    MAC::Addr dest_mac;

    if (next_hop == IP4::ADDR_BCAST) {
      dest_mac = MAC::BROADCAST;
    } else {

#ifdef ARP_PASSTHROUGH
      extern MAC::Addr linux_tap_device;
      dest_mac = linux_tap_device;
#else
      // If we don't have a cached IP, perform address resolution
      auto cache_entry = cache_.find(next_hop);
      if (UNLIKELY(cache_entry == cache_.end())) {
        PRINT("<ARP> No cache entry for IP %s.  Resolving. \n", next_hop.to_string().c_str());
        await_resolution(std::move(pckt), next_hop);
        return;
      }

      // Get MAC from cache
      dest_mac = cache_[next_hop].mac();
#endif

      PRINT("<ARP> Found cache entry for IP %s -> %s \n",
            next_hop.to_string().c_str(), dest_mac.to_string().c_str());
    }

    // Move chain to linklayer
    linklayer_out_(std::move(pckt), dest_mac, Ethertype::IP4);
  }
Example #19
0
  void Arp::transmit(Packet_ptr pckt) {
    assert(pckt->size());

    /** Get destination IP from IP header */
    IP4::ip_header* iphdr = reinterpret_cast<IP4::ip_header*>(pckt->buffer()
                                                              + sizeof(Ethernet::header));
    IP4::addr sip = iphdr->saddr;
    IP4::addr dip = pckt->next_hop();

    debug2("<ARP -> physical> Transmitting %i bytes to %s\n",
           pckt->size(), dip.str().c_str());

    Ethernet::addr dest_mac;

    if (iphdr->daddr == IP4::INADDR_BCAST) {
      // When broadcasting our source IP should be either
      // our own IP or 0.0.0.0

      if (sip != inet_.ip_addr() && sip != IP4::INADDR_ANY) {
        debug2("<ARP> Dropping outbound broadcast packet due to "
               "invalid source IP %s\n",  sip.str().c_str());
        return;
      }
      // mui importante
      dest_mac = Ethernet::BROADCAST_FRAME;

    } else {
      if (sip != inet_.ip_addr()) {
        debug2("<ARP -> physical> Not bound to source IP %s. My IP is %s. DROP!\n",
               sip.str().c_str(), inet_.ip_addr().str().c_str());
        return;
      }

      // If we don't have a cached IP, perform address resolution
      if (!is_valid_cached(dip)) {
        arp_resolver_(std::move(pckt));
        return;
      }

      // Get MAC from cache
      dest_mac = cache_[dip].mac_;
    }

    /** Attach next-hop mac and ethertype to ethernet header */
    auto* ethhdr = reinterpret_cast<Ethernet::header*>(pckt->buffer());
    ethhdr->src  = mac_;
    ethhdr->dest = dest_mac;
    ethhdr->type = Ethernet::ETH_IP4;

    /** Update chain as well */
    auto* next = pckt->tail();
    while(next) {
      auto* headur = reinterpret_cast<Ethernet::header*>(next->buffer());
      headur->src  = mac_;
      headur->dest = dest_mac;
      headur->type = Ethernet::ETH_IP4;
      next = next->tail();
    }

    debug2("<ARP -> physical> Sending packet to %s\n", mac_.str().c_str());
    linklayer_out_(std::move(pckt));
  }
Example #20
0
void Listener::segment_arrived(Packet_ptr packet) {
  debug2("<Listener::segment_arrived> Received packet: %s\n",
    packet->to_string().c_str());

  auto it = std::find_if(syn_queue_.begin(), syn_queue_.end(),
    [dest = packet->source()]
    (Connection_ptr conn) {
      return conn->remote() == dest;
    });

  // if it's an reply to any of our half-open connections
  if(it != syn_queue_.end())
  {
    auto conn = *it;
    debug("<Listener::segment_arrived> Found packet receiver: %s\n",
      conn->to_string().c_str());
    conn->segment_arrived(std::move(packet));
    debug2("<Listener::segment_arrived> Connection done handling segment\n");
    return;
  }
  // if it's a new attempt (SYN)
  else
  {
    // don't waste time if the packet does not have SYN
    if(UNLIKELY(not packet->isset(SYN) or packet->has_tcp_data()))
    {
      host_.send_reset(*packet);
      return;
    }

    // Stat increment number of connection attempts
    host_.connection_attempts_++;

    // if we don't like this client, do nothing
    if(UNLIKELY(on_accept_(packet->source()) == false))
      return;

    // remove oldest connection if queue is full
    debug2("<Listener::segment_arrived> SynQueue: %u\n", syn_queue_.size());
    // SYN queue is full
    if(syn_queue_.size() >= host_.max_syn_backlog())
    {
      debug2("<Listener::segment_arrived> Queue is full\n");
      Expects(not syn_queue_.empty());
      debug("<Listener::segment_arrived> Connection %s dropped to make room for new connection\n",
        syn_queue_.back()->to_string().c_str());

      syn_queue_.pop_back();
    }

    auto& conn = *(syn_queue_.emplace(
      syn_queue_.cbegin(),
      std::make_shared<Connection>(host_, packet->destination(), packet->source(), ConnectCallback{this, &Listener::connected})
      )
    );
    conn->_on_cleanup({this, &Listener::remove});
    // Open connection
    conn->open(false);
    Ensures(conn->is_listening());
    debug("<Listener::segment_arrived> Connection %s created\n",
      conn->to_string().c_str());
    conn->segment_arrived(std::move(packet));
    debug2("<Listener::segment_arrived> Connection done handling segment\n");
    return;
  }
  debug2("<Listener::segment_arrived> No receipent\n");
}
Example #21
0
void Inet::error_report(Error& err, Packet_ptr orig_pckt) {
  // if its a forged packet, it might be too small
  if (orig_pckt->size() < 40) return;

  auto pckt_ip4 = static_unique_ptr_cast<PacketIP4>(std::move(orig_pckt));
  // Get the destination to the original packet
  const Socket dest =
    [] (std::unique_ptr<PacketIP4>& pkt)->Socket
    {
      // if its a forged packet, it might not be IPv4
      if (pkt->is_ipv4() == false) return {};
      // switch on IP4 protocol
      switch (pkt->ip_protocol()) {
        case Protocol::UDP: {
          const auto& udp = static_cast<const PacketUDP&>(*pkt);
          return udp.destination();
        }
        case Protocol::TCP: {
          auto tcp = tcp::Packet4_view(std::move(pkt));
          auto dst = tcp.destination();
          pkt = static_unique_ptr_cast<PacketIP4>(tcp.release());
          return dst;
        }
        default:
          return {};
      }
    }(pckt_ip4);

  bool too_big = false;
  if (err.is_icmp()) {
    auto* icmp_err = dynamic_cast<ICMP_error*>(&err);
    if (icmp_err == nullptr) {
      return; // not an ICMP error
    }

    if (icmp_err->is_too_big()) {
      // If Path MTU Discovery is not enabled, ignore the ICMP Datagram Too Big message
      if (not ip4_.path_mtu_discovery())
        return;

      too_big = true;

      // We have received a response to a packet with an MTU that is too big for a node in the path,
      // and the packet has been dropped (the original packet was too big and the Don't Fragment bit was set)

      // Notify every protocol of the received MTU if any of the protocol's connections use the given
      // path (based on destination address)

      // Also need to notify the instance that sent the packet that the packet has been dropped, so
      // it can retransmit it

      // A Destination Unreachable: Fragmentation Needed ICMP error message has been received
      // And we'll notify the IP layer of the received MTU value
      // IP will create the path if it doesn't exist and only update the value if
      // the value is smaller than the already registered pmtu for this path/destination
      // If the received MTU value is zero, the method will use the original packet's Total Length
      // and Header Length values to estimate a new Path MTU value
      ip4_.update_path(dest, icmp_err->pmtu(), too_big, pckt_ip4->ip_total_length(), pckt_ip4->ip_header_length());

      // The actual MTU for the path is set in the error object
      icmp_err->set_pmtu(ip4_.pmtu(dest));
    }
  }

  if (too_big) {
    // Notify both transport layers in case they use the path
    udp_.error_report(err, dest);
    tcp_.error_report(err, dest);
  } else if (pckt_ip4->ip_protocol() == Protocol::UDP) {
    udp_.error_report(err, dest);
  } else if (pckt_ip4->ip_protocol() == Protocol::TCP) {
    tcp_.error_report(err, dest);
  }
}