/** @file

Copyright (c) 2005 - 2018, Intel Corporation. All rights reserved.<BR>
SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include "Ip4Impl.h"

IP4_ICMP_CLASS
  mIcmpClass[] = {
  { ICMP_ECHO_REPLY,        ICMP_QUERY_MESSAGE   },
  { 1,                      ICMP_INVALID_MESSAGE },
  { 2,                      ICMP_INVALID_MESSAGE },
  { ICMP_DEST_UNREACHABLE,  ICMP_ERROR_MESSAGE   },
  { ICMP_SOURCE_QUENCH,     ICMP_ERROR_MESSAGE   },
  { ICMP_REDIRECT,          ICMP_ERROR_MESSAGE   },
  { 6,                      ICMP_INVALID_MESSAGE },
  { 7,                      ICMP_INVALID_MESSAGE },
  { ICMP_ECHO_REQUEST,      ICMP_QUERY_MESSAGE   },
  { 9,                      ICMP_INVALID_MESSAGE },
  { 10,                     ICMP_INVALID_MESSAGE },
  { ICMP_TIME_EXCEEDED,     ICMP_ERROR_MESSAGE   },
  { ICMP_PARAMETER_PROBLEM, ICMP_ERROR_MESSAGE   },
  { ICMP_TIMESTAMP,         ICMP_QUERY_MESSAGE   },
  { 14,                     ICMP_INVALID_MESSAGE },
  { ICMP_INFO_REQUEST,      ICMP_QUERY_MESSAGE   },
  { ICMP_INFO_REPLY,        ICMP_QUERY_MESSAGE   },
};

EFI_IP4_ICMP_TYPE
  mIp4SupportedIcmp[23] = {
  { ICMP_ECHO_REPLY,        ICMP_DEFAULT_CODE         },

  { ICMP_DEST_UNREACHABLE,  ICMP_NET_UNREACHABLE      },
  { ICMP_DEST_UNREACHABLE,  ICMP_HOST_UNREACHABLE     },
  { ICMP_DEST_UNREACHABLE,  ICMP_PROTO_UNREACHABLE    },
  { ICMP_DEST_UNREACHABLE,  ICMP_PORT_UNREACHABLE     },
  { ICMP_DEST_UNREACHABLE,  ICMP_FRAGMENT_FAILED      },
  { ICMP_DEST_UNREACHABLE,  ICMP_SOURCEROUTE_FAILED   },
  { ICMP_DEST_UNREACHABLE,  ICMP_NET_UNKNOWN          },
  { ICMP_DEST_UNREACHABLE,  ICMP_HOST_UNKNOWN         },
  { ICMP_DEST_UNREACHABLE,  ICMP_SOURCE_ISOLATED      },
  { ICMP_DEST_UNREACHABLE,  ICMP_NET_PROHIBITED       },
  { ICMP_DEST_UNREACHABLE,  ICMP_HOST_PROHIBITED      },
  { ICMP_DEST_UNREACHABLE,  ICMP_NET_UNREACHABLE_TOS  },
  { ICMP_DEST_UNREACHABLE,  ICMP_HOST_UNREACHABLE_TOS },

  { ICMP_SOURCE_QUENCH,     ICMP_DEFAULT_CODE         },

  { ICMP_REDIRECT,          ICMP_NET_REDIRECT         },
  { ICMP_REDIRECT,          ICMP_HOST_REDIRECT        },
  { ICMP_REDIRECT,          ICMP_NET_TOS_REDIRECT     },
  { ICMP_REDIRECT,          ICMP_HOST_TOS_REDIRECT    },

  { ICMP_ECHO_REQUEST,      ICMP_DEFAULT_CODE         },

  { ICMP_TIME_EXCEEDED,     ICMP_TIMEOUT_IN_TRANSIT   },
  { ICMP_TIME_EXCEEDED,     ICMP_TIMEOUT_REASSEMBLE   },

  { ICMP_PARAMETER_PROBLEM, ICMP_DEFAULT_CODE         },
};

/**
  Process the ICMP redirect. Find the instance then update
  its route cache.

  All kinds of redirect is treated as host redirect as
  specified by RFC1122 3.3.1.2:
  "Since the subnet mask appropriate to the destination
  address is generally not known, a Network Redirect
  message SHOULD be treated identically to a Host Redirect
  message;"

  @param[in]  IpSb               The IP4 service binding instance that received
                                 the packet.
  @param[in]  Head               The IP head of the received ICMPpacket.
  @param[in]  Packet             The content of the ICMP redirect packet with IP
                                 head removed.
  @param[in]  Icmp               The buffer to store the ICMP error message if
                                 something is wrong.

  @retval EFI_INVALID_PARAMETER  The parameter is invalid
  @retval EFI_SUCCESS            Successfully updated the route caches

**/
EFI_STATUS
Ip4ProcessIcmpRedirect (
  IN IP4_SERVICE          *IpSb,
  IN IP4_HEAD             *Head,
  IN NET_BUF              *Packet,
  IN IP4_ICMP_ERROR_HEAD  *Icmp
  )
{
  LIST_ENTRY             *Entry;
  IP4_PROTOCOL           *Ip4Instance;
  IP4_ROUTE_CACHE_ENTRY  *CacheEntry;
  IP4_INTERFACE          *IpIf;
  IP4_ADDR               Gateway;
  IP4_ADDR               Src;
  IP4_ADDR               Dst;

  //
  // Find the interface whose IP address is the source of the
  // orgianl IP packet.
  //
  IpIf    = Ip4FindInterface (IpSb, NTOHL (Icmp->IpHead.Src));
  Gateway = NTOHL (Icmp->Fourth);

  //
  // discard the packet if the new gateway address it specifies
  // is not on the same connected net through which the Redirect
  // arrived. (RFC1122 3.2.2.2).
  //
  if ((IpIf == NULL) || !IP4_NET_EQUAL (Gateway, IpIf->Ip, IpIf->SubnetMask)) {
    NetbufFree (Packet);
    return EFI_INVALID_PARAMETER;
  }

  //
  // Update each IP child's route cache on the interface.
  //
  NET_LIST_FOR_EACH (Entry, &IpIf->IpInstances) {
    Ip4Instance = NET_LIST_USER_STRUCT (Entry, IP4_PROTOCOL, AddrLink);

    if (Ip4Instance->RouteTable == NULL) {
      continue;
    }

    Dst        = NTOHL (Icmp->IpHead.Dst);
    Src        = NTOHL (Icmp->IpHead.Src);
    CacheEntry = Ip4FindRouteCache (Ip4Instance->RouteTable, Dst, Src);

    //
    // Only update the route cache's gateway if the source of the
    // Redirect is the current first-hop gateway
    //
    if ((CacheEntry != NULL) && (NTOHL (Head->Src) == CacheEntry->NextHop)) {
      CacheEntry->NextHop = Gateway;
    }
  }

  NetbufFree (Packet);
  return EFI_SUCCESS;
}

/**
  Process the ICMP error packet. If it is an ICMP redirect packet,
  update call Ip4ProcessIcmpRedirect to update the IP instance's
  route cache, otherwise, deliver the packet to upper layer.

  @param[in]  IpSb               The IP4 service that received the packet.
  @param[in]  Head               The IP4 head of the ICMP error packet
  @param[in]  Packet             The content of the ICMP error with IP4 head
                                 removed.

  @retval EFI_SUCCESS            The ICMP error is processed successfully.
  @retval EFI_INVALID_PARAMETER  The packet is invalid
  @retval Others                 Failed to process the packet.

**/
EFI_STATUS
Ip4ProcessIcmpError (
  IN IP4_SERVICE  *IpSb,
  IN IP4_HEAD     *Head,
  IN NET_BUF      *Packet
  )
{
  IP4_ICMP_ERROR_HEAD  Icmp;

  if (Packet->TotalSize < sizeof (Icmp)) {
    NetbufFree (Packet);
    return EFI_INVALID_PARAMETER;
  }

  NetbufCopy (Packet, 0, sizeof (Icmp), (UINT8 *)&Icmp);

  //
  // If it is an ICMP redirect error, update the route cache
  // as RFC1122. Otherwise, demultiplex it to IP instances.
  //
  if (Icmp.Head.Type == ICMP_REDIRECT) {
    return Ip4ProcessIcmpRedirect (IpSb, Head, Packet, &Icmp);
  }

  IP4_GET_CLIP_INFO (Packet)->Status = EFI_ICMP_ERROR;
  return Ip4Demultiplex (IpSb, Head, Packet, NULL, 0);
}

/**
  Replay an ICMP echo request.

  @param[in]  IpSb               The IP4 service that receivd the packet
  @param[in]  Head               The IP4 head of the ICMP error packet
  @param[in]  Packet             The content of the ICMP error with IP4 head
                                 removed.

  @retval EFI_OUT_OF_RESOURCES   Failed to allocate resource.
  @retval EFI_SUCCESS            The ICMP Echo request is successfully answered.
  @retval Others                 Failed to answer the ICMP echo request.

**/
EFI_STATUS
Ip4IcmpReplyEcho (
  IN IP4_SERVICE  *IpSb,
  IN IP4_HEAD     *Head,
  IN NET_BUF      *Packet
  )
{
  IP4_ICMP_QUERY_HEAD  *Icmp;
  NET_BUF              *Data;
  EFI_STATUS           Status;
  IP4_HEAD             ReplyHead;

  //
  // make a copy the packet, it is really a bad idea to
  // send the MNP's buffer back to MNP.
  //
  Data = NetbufDuplicate (Packet, NULL, IP4_MAX_HEADLEN);

  if (Data == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    goto ON_EXIT;
  }

  //
  // Change the ICMP type to echo reply, exchange the source
  // and destination, then send it. The source is updated to
  // use specific destination. See RFC1122. SRR/RR option
  // update is omitted.
  //
  Icmp = (IP4_ICMP_QUERY_HEAD *)NetbufGetByte (Data, 0, NULL);
  ASSERT (Icmp != NULL);
  Icmp->Head.Type     = ICMP_ECHO_REPLY;
  Icmp->Head.Checksum = 0;
  Icmp->Head.Checksum = (UINT16)(~NetblockChecksum ((UINT8 *)Icmp, Data->TotalSize));

  ReplyHead.Tos      = 0;
  ReplyHead.Fragment = 0;
  ReplyHead.Ttl      = 64;
  ReplyHead.Protocol = EFI_IP_PROTO_ICMP;
  ReplyHead.Src      = 0;

  //
  // Ip4Output will select a source for us
  //
  ReplyHead.Dst = Head->Src;

  Status = Ip4Output (
             IpSb,
             NULL,
             Data,
             &ReplyHead,
             NULL,
             0,
             IP4_ALLZERO_ADDRESS,
             Ip4SysPacketSent,
             NULL
             );
  if (EFI_ERROR (Status)) {
    NetbufFree (Data);
  }

ON_EXIT:
  NetbufFree (Packet);
  return Status;
}

/**
  Process the ICMP query message. If it is an ICMP echo
  request, answer it. Otherwise deliver it to upper layer.

  @param[in]  IpSb               The IP4 service that receivd the packet
  @param[in]  Head               The IP4 head of the ICMP query packet
  @param[in]  Packet             The content of the ICMP query with IP4 head
                                 removed.

  @retval EFI_INVALID_PARAMETER  The packet is invalid
  @retval EFI_SUCCESS            The ICMP query message is processed
  @retval Others                 Failed to process ICMP query.

**/
EFI_STATUS
Ip4ProcessIcmpQuery (
  IN IP4_SERVICE  *IpSb,
  IN IP4_HEAD     *Head,
  IN NET_BUF      *Packet
  )
{
  IP4_ICMP_QUERY_HEAD  Icmp;

  if (Packet->TotalSize < sizeof (Icmp)) {
    NetbufFree (Packet);
    return EFI_INVALID_PARAMETER;
  }

  NetbufCopy (Packet, 0, sizeof (Icmp), (UINT8 *)&Icmp);

  if (Icmp.Head.Type == ICMP_ECHO_REQUEST) {
    return Ip4IcmpReplyEcho (IpSb, Head, Packet);
  }

  return Ip4Demultiplex (IpSb, Head, Packet, NULL, 0);
}

/**
  Handle the ICMP packet. First validate the message format,
  then according to the message types, process it as query or
  error packet.

  @param[in]  IpSb               The IP4 service that receivd the packet.
  @param[in]  Head               The IP4 head of the ICMP query packet.
  @param[in]  Packet             The content of the ICMP query with IP4 head
                                 removed.

  @retval EFI_INVALID_PARAMETER  The packet is malformatted.
  @retval EFI_SUCCESS            The ICMP message is successfully processed.
  @retval Others                 Failed to handle ICMP packet.

**/
EFI_STATUS
Ip4IcmpHandle (
  IN IP4_SERVICE  *IpSb,
  IN IP4_HEAD     *Head,
  IN NET_BUF      *Packet
  )
{
  IP4_ICMP_HEAD  Icmp;
  UINT16         Checksum;

  if (Packet->TotalSize < sizeof (Icmp)) {
    goto DROP;
  }

  NetbufCopy (Packet, 0, sizeof (Icmp), (UINT8 *)&Icmp);

  if (Icmp.Type > ICMP_TYPE_MAX) {
    goto DROP;
  }

  Checksum = (UINT16)(~NetbufChecksum (Packet));
  if ((Icmp.Checksum != 0) && (Checksum != 0)) {
    goto DROP;
  }

  if (mIcmpClass[Icmp.Type].IcmpClass == ICMP_ERROR_MESSAGE) {
    return Ip4ProcessIcmpError (IpSb, Head, Packet);
  } else if (mIcmpClass[Icmp.Type].IcmpClass == ICMP_QUERY_MESSAGE) {
    return Ip4ProcessIcmpQuery (IpSb, Head, Packet);
  }

DROP:
  NetbufFree (Packet);
  return EFI_INVALID_PARAMETER;
}