/*
 * icmperror.{cc,hh} -- element constructs ICMP error packets
 * Robert Morris, Eddie Kohler
 *
 * Copyright (c) 1999-2000 Massachusetts Institute of Technology
 * Copyright (c) 2003 International Computer Science Institute
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, subject to the conditions
 * listed in the Click LICENSE file. These conditions include: you must
 * preserve this copyright notice, and you cannot mention the copyright
 * holders in advertising related to the Software without their permission.
 * The Software is provided WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED. This
 * notice is a summary of the Click LICENSE file; the license in that file is
 * legally binding.
 */

#include <click/config.h>
#include <clicknet/icmp.h>
#include <clicknet/ip.h>
#include "icmperror.hh"
#include <click/ipaddress.hh>
#include <click/confparse.hh>
#include <click/error.hh>
#include <click/glue.hh>
#include <click/packet_anno.hh>
#include <click/nameinfo.hh>
CLICK_DECLS

ICMPError::ICMPError()
  : _type(-1), _code(-1)
{
}

ICMPError::~ICMPError()
{
}

bool
ICMPError::is_error_type(int type)
{
    return type == ICMP_UNREACH || type == ICMP_SOURCEQUENCH
	|| type == ICMP_REDIRECT ||	type == ICMP_TIMXCEED
	|| type == ICMP_PARAMPROB;
}

int
ICMPError::configure(Vector<String> &conf, ErrorHandler *errh)
{
    String code_str = "0";
    _mtu = 576;
    _pmtu = 0;
    if (cp_va_parse(conf, this, errh,
		    cpIPAddress, "source IP address", &_src_ip,
		    cpNamedInteger, "ICMP error type", NameInfo::T_ICMP_TYPE, &_type,
		    cpOptional,
		    cpWord, "ICMP error code", &code_str,
		    cpIPAddressList, "bad IP addresses", &_bad_addrs,
		    cpKeywords,
		    "BADADDRS", cpIPAddressList, "bad IP addresses", &_bad_addrs,
		    "MTU", cpUnsigned, "MTU", &_mtu,
		    "PMTU", cpUnsigned, "Next-Hop MTU", &_pmtu,
		    cpEnd) < 0)
	return -1;
    if (_type < 0 || _type > 255)
	return errh->error("ICMP type must be between 0 and 255");
    else if (!is_error_type(_type))
	return errh->error("ICMP type %d is not an error type", _type);
    if (!NameInfo::query_int(NameInfo::T_ICMP_CODE + _type, this, code_str, &_code)
	|| _code < 0 || _code > 255)
	return errh->error("argument 2 takes ICMP code (integer between 0 and 255)");
    return 0;
}

/*
 * Is an IP address unicast?
 */
bool
ICMPError::unicast(struct in_addr aa) const
{
  uint32_t a = aa.s_addr;
  uint32_t ha = ntohl(a);

  /* limited broadcast */
  if(ha == 0xffffffff)
    return(0);
  
  /* Class D multicast */
  if((ha & 0xf0000000u) == 0xe0000000u)
    return(0);

  /* limited broadcast */
  if (_bad_addrs.contains(a))
    return 0;
  
  return(1);
}

/*
 * Is a source IP address valid as defined in RFC1812 5.3.7
 * or 4.2.2.11 or 4.2.3.1?
 */
bool
ICMPError::valid_source(struct in_addr aa) const
{
  unsigned int a = aa.s_addr;
  unsigned int ha = ntohl(a);
  unsigned net = (ha >> 24) & 0xff;

  /* broadcast, multicast, or (local) directed broadcast */
  if(unicast(aa) == 0)
    return(0);

  /* local net or host: */
  if(net == 0)
    return(0);

  /* 127.0.0.1 */
  if(net == 127)
    return(0);

  /* Class E */
  if((net & 0xf0) == 0xf0)
    return(0);

  return(1);
}

/*
 * Does a packet contain a source route option?
 */
const uint8_t *
ICMPError::valid_source_route(const click_ip *iph)
{
  const uint8_t *oa = (const uint8_t *)iph;
  int hlen = iph->ip_hl << 2;

  for (int oi = sizeof(click_ip); oi < hlen; ) {
    // handle one-byte options
    unsigned type = oa[oi];
    if (type == IPOPT_NOP) {
      oi++;
      continue;
    } else if (type == IPOPT_EOL)
      return 0;

    // otherwise, get option length
    int xlen = oa[oi + 1];
    if (xlen < 2 || oi + xlen > hlen)
      return 0;
    else if ((type == IPOPT_LSRR || type == IPOPT_SSRR)
	     && oa[oi + 2] >= 4 && oa[oi + 2] % 4 == 0
	     && oa[oi + 2] <= xlen + 1)
      return oa + oi;
    else
      oi += xlen;
  }

  return 0;
}

Packet *
ICMPError::simple_action(Packet *p)
{
  WritablePacket *q = 0;
  const click_ip *ipp = p->ip_header();
  const uint8_t *source_route;
  click_ip *nip;
  click_icmp *icp;
  unsigned hlen, xlen;
  static int id = 1;

  if (!ipp)
    goto out;

  hlen = ipp->ip_hl << 2;

  /* These "don'ts" are from RFC1812 4.3.2.7: */

  /* Don't reply to ICMP error messages. */
  if(ipp->ip_p == IP_PROTO_ICMP) {
    const click_icmp *icmph = p->icmp_header();
    if(hlen + 4 > p->length() || is_error_type(icmph->icmp_type))
      goto out;
  }

  /* Don't respond to packets with IP broadcast destinations. */
  if(!unicast(ipp->ip_dst))
    goto out;

  /* Don't respond to e.g. Ethernet broadcasts or multicasts. */
  if (p->packet_type_anno() == Packet::BROADCAST || p->packet_type_anno() == Packet::MULTICAST)
    goto out;

  /* Don't respond is src is net 0 or invalid. */
  if(!valid_source(ipp->ip_src))
    goto out;

  /* Don't respond to fragments other than the first. */
  if(!IP_FIRSTFRAG(ipp))
    goto out;

  source_route = valid_source_route(ipp);
  if (source_route) {
    /* Don't send a redirect for a source-routed packet. 5.2.7.2 */
    if (_type == ICMP_REDIRECT)
      goto out;

    /* Ignore source route if ICMP Parameter Problem concerns the source
       route. 4.3.2.6 */
    if (_type == ICMP_PARAMPROB && _code == ICMP_PARAMPROB_ERRATPTR
	&& source_route <= ((const uint8_t *)ipp + ICMP_PARAMPROB_ANNO(p))
	&& ((const uint8_t *)ipp + ICMP_PARAMPROB_ANNO(p)) < (source_route + source_route[1]))
      source_route = 0;
  }

  // maximum size of ICMP packet is 576 bytes. 4.3.2.3
  q = Packet::make(_mtu);	// we made it configurable
  if (!q)
    goto out;

  // prepare IP header; guaranteed that packet data is aligned
  nip = reinterpret_cast<click_ip *>(q->data());
  nip->ip_v = 4;
  nip->ip_tos = 0;		// XXX should be same as incoming datagram?
  nip->ip_id = htons(id++);
  nip->ip_off = 0;
  nip->ip_ttl = 200;
  nip->ip_p = IP_PROTO_ICMP;
  nip->ip_sum = 0;
  nip->ip_src = _src_ip.in_addr();
  nip->ip_dst = ipp->ip_src;
  
  // include reversed source route if appropriate 4.3.2.6
  if (source_route) {
    uint8_t *o = q->data() + sizeof(click_ip);
    int olen = source_route[2] - 1;
    o[0] = source_route[0];	// copy option type
    o[1] = olen;
    o[2] = 4;
    o[olen] = IPOPT_EOL;
    o += 3;
    for (const uint8_t *oo = source_route + source_route[2] - 5; oo >= source_route + 3; oo -= 4, o += 4)
      memcpy(o, oo, 4);
    nip->ip_hl = (sizeof(click_ip) + olen + 3) >> 2;
  } else
    nip->ip_hl = sizeof(click_ip) >> 2;
  q->set_ip_header(nip, nip->ip_hl << 2);

  // now, prepare ICMP header
  icp = q->icmp_header();
  icp->icmp_type = _type;
  icp->icmp_code = _code;
  icp->icmp_cksum = 0;
  icp->padding = 0;

  // set ICMP particulars
  if (_type == ICMP_PARAMPROB && _code == ICMP_PARAMPROB_ERRATPTR)
    /* Set the Parameter Problem pointer. */
    ((click_icmp_paramprob *) icp)->icmp_pointer = ICMP_PARAMPROB_ANNO(p);
  if (_type == ICMP_REDIRECT)
    ((click_icmp_redirect *) icp)->icmp_gateway = p->dst_ip_anno();
  if (_type == ICMP_UNREACH && _code == ICMP_UNREACH_NEEDFRAG)
    ((click_icmp_needfrag *) icp)->icmp_nextmtu = htons(_pmtu);

  // copy packet contents
  xlen = q->end_data() - (uint8_t *)(icp + 1);
  if ((int)xlen > p->network_length()) {
    q->take(xlen - p->network_length());
    xlen = p->network_length();
  }
  memcpy((uint8_t *)(icp + 1), p->network_header(), xlen);
  icp->icmp_cksum = click_in_cksum((unsigned char *)icp, sizeof(click_icmp) + xlen);

  // finish off IP header
  nip->ip_len = htons(q->length());
  nip->ip_sum = click_in_cksum((unsigned char *)nip, nip->ip_hl << 2);

  // set annotations
  q->set_dst_ip_anno(IPAddress(nip->ip_dst));
  SET_FIX_IP_SRC_ANNO(q, 1);
  q->timestamp_anno().set_now();

 out:
  p->kill();
  return(q);
}

CLICK_ENDDECLS
EXPORT_ELEMENT(ICMPError)
ELEMENT_MT_SAFE(ICMPError)


syntax highlighted by Code2HTML, v. 0.9.1