/* -*- c-basic-offset: 2 -*- */
/*
 * icmprewriter.{cc,hh} -- rewrites ICMP non-echoes and non-replies
 * Eddie Kohler
 *
 * Copyright (c) 2000 Mazu Networks, Inc.
 *
 * 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 "icmprewriter.hh"
#include <clicknet/icmp.h>
#include <click/confparse.hh>
#include <click/straccum.hh>
#include <click/error.hh>
CLICK_DECLS

ICMPRewriter::ICMPRewriter()
{
}

ICMPRewriter::~ICMPRewriter()
{
}

int
ICMPRewriter::configure(Vector<String> &conf, ErrorHandler *errh)
{
  String arg;
  if (cp_va_parse(conf, this, errh,
		  cpArgument, "rewriters", &arg,
		  cpEnd) < 0)
    return -1;

  Vector<String> words;
  cp_spacevec(arg, words);

  for (int i = 0; i < words.size(); i++) {
    if (Element *e = cp_element(words[i], this, errh)) {
      if (IPRw *rw = static_cast<IPRw *>(e->cast("IPRw")))
	_maps.push_back(rw);
      else if (ICMPPingRewriter *rw = static_cast<ICMPPingRewriter *>(e->cast("ICMPPingRewriter")))
	_ping_maps.push_back(rw);
      else
	errh->error("element `%s' is not an IP rewriter", words[i].c_str());
    }
  }

  if (_maps.size() == 0 && _ping_maps.size() == 0)
    return errh->error("no IP rewriters supplied");
  return 0;
}

void
ICMPRewriter::rewrite_packet(WritablePacket *p, click_ip *embedded_iph,
			     click_udp *embedded_udph, const IPFlowID &flow,
			     IPRw::Mapping *mapping)
{
  click_ip *iph = p->ip_header();
  click_icmp *icmph = p->icmp_header();

  // XXX incremental checksums?
  
  IPFlowID new_flow = mapping->flow_id().rev();
  
  // change IP header destination if appropriate
  if (IPAddress(iph->ip_dst) == flow.saddr()) {
    unsigned hlen = iph->ip_hl << 2;
    iph->ip_dst = new_flow.saddr();
    iph->ip_sum = 0;
    iph->ip_sum = click_in_cksum((unsigned char *)iph, hlen);
  }
  
  // don't bother patching embedded IP or UDP checksums
  embedded_iph->ip_src = new_flow.saddr();
  embedded_iph->ip_dst = new_flow.daddr();
  embedded_udph->uh_sport = new_flow.sport();
  embedded_udph->uh_dport = new_flow.dport();

  // but must patch ICMP checksum
  icmph->icmp_cksum = 0;
  icmph->icmp_cksum = click_in_cksum((unsigned char *)icmph, p->length() - p->transport_header_offset());
}

void
ICMPRewriter::rewrite_ping_packet(WritablePacket *p, click_ip *embedded_iph,
				  click_icmp_echo *embedded_icmph, const IPFlowID &flow,
				  ICMPPingRewriter::Mapping *mapping)
{
  click_ip *iph = p->ip_header();
  click_icmp *icmph = p->icmp_header();

  // XXX incremental checksums?
  
  IPFlowID new_flow = mapping->flow_id().rev();
  
  // change IP header destination if appropriate
  if (IPAddress(iph->ip_dst) == flow.saddr()) {
    unsigned hlen = iph->ip_hl << 2;
    iph->ip_dst = new_flow.saddr();
    iph->ip_sum = 0;
    iph->ip_sum = click_in_cksum((unsigned char *)iph, hlen);
  }
  
  // don't bother patching embedded ICMP checksum
  embedded_iph->ip_src = new_flow.saddr();
  embedded_iph->ip_dst = new_flow.daddr();
  embedded_icmph->icmp_identifier = new_flow.sport();

  // but must patch ICMP checksum
  icmph->icmp_cksum = 0;
  icmph->icmp_cksum = click_in_cksum((unsigned char *)icmph, p->length() - p->transport_header_offset());
}

Packet *
ICMPRewriter::simple_action(Packet *p_in)
{
  WritablePacket *p = p_in->uniqueify();
  if (!p)
    return 0;
  else if (p->ip_header()->ip_p != IP_PROTO_ICMP) {
    p->kill();
    return 0;
  }
  
  click_icmp *icmph = p->icmp_header();
  switch (icmph->icmp_type) {

   case ICMP_UNREACH:
   case ICMP_TIMXCEED:
   case ICMP_PARAMPROB:
   case ICMP_SOURCEQUENCH:
   case ICMP_REDIRECT: {
     // check length of embedded IP header
     click_ip *embedded_iph = reinterpret_cast<click_ip *>(icmph + 1);
     unsigned hlen = embedded_iph->ip_hl << 2;
     if (p->transport_length() < (int)(sizeof(click_icmp) + hlen + 8)
	 || hlen < sizeof(click_ip))
       goto bad;

     // check protocol
     int embedded_p = embedded_iph->ip_p;

     if (embedded_p == IP_PROTO_UDP || embedded_p == IP_PROTO_TCP) {
       // TCP or UDP
       // create flow ID
       click_udp *embedded_udph = reinterpret_cast<click_udp *>(reinterpret_cast<unsigned char *>(embedded_iph) + hlen);
       IPFlowID flow(embedded_iph->ip_src, embedded_udph->uh_sport, embedded_iph->ip_dst, embedded_udph->uh_dport);
     
       IPRw::Mapping *mapping = 0;
       for (int i = 0; i < _maps.size() && !mapping; i++)
	 mapping = _maps[i]->get_mapping(embedded_p, flow.rev());
       if (!mapping)
	 goto unmapped;
     
       rewrite_packet(p, embedded_iph, embedded_udph, flow, mapping);
       
     } else if (embedded_p == IP_PROTO_ICMP) {
       // ICMP
       click_icmp_sequenced *embedded_icmph = reinterpret_cast<click_icmp_sequenced *>(reinterpret_cast<unsigned char *>(embedded_iph) + hlen);
       
       int embedded_type = embedded_icmph->icmp_type;
       if (embedded_type != ICMP_ECHO && embedded_type != ICMP_ECHOREPLY)
	 goto unmapped;
       bool ask_for_request = (embedded_type != ICMP_ECHO);
       
       IPFlowID flow(embedded_iph->ip_src, embedded_icmph->icmp_identifier, embedded_iph->ip_dst, embedded_icmph->icmp_identifier);

       ICMPPingRewriter::Mapping *mapping = 0;
       for (int i = 0; i < _ping_maps.size() && !mapping; i++)
	 mapping = _ping_maps[i]->get_mapping(ask_for_request, flow.rev());
       if (!mapping)
	 goto unmapped;

       rewrite_ping_packet(p, embedded_iph, embedded_icmph, flow, mapping);
       
     } else
       goto unmapped;
       
     return p;
   }

   bad:
    p->kill();
    return 0;

   unmapped:
   default:
    checked_output_push(1, p);
    return 0;

  }
}

CLICK_ENDDECLS
ELEMENT_REQUIRES(IPRw ICMPPingRewriter)
EXPORT_ELEMENT(ICMPRewriter)


syntax highlighted by Code2HTML, v. 0.9.1