/*
 * ethernet/arp.c: IP v4 ARP node
 *
 * Copyright (c) 2010 Cisco and/or its affiliates.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at:
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <vnet/arp/arp.h>
#include <vnet/arp/arp_packet.h>

#include <vnet/fib/ip4_fib.h>

typedef struct
{
  ip4_address_t lo_addr;
  ip4_address_t hi_addr;
  u32 fib_index;
} ethernet_proxy_arp_t;

typedef struct arp_proxy_main_t_
{
  /** Per interface state */
  bool *enabled_by_sw_if_index;

  /* Proxy arp vector */
  ethernet_proxy_arp_t *proxy_arps;
} arp_proxy_main_t;

arp_proxy_main_t arp_proxy_main;

void
proxy_arp_walk (proxy_arp_walk_t cb, void *data)
{
  arp_proxy_main_t *am = &arp_proxy_main;
  ethernet_proxy_arp_t *pa;

  vec_foreach (pa, am->proxy_arps)
  {
    if (!cb (&pa->lo_addr, &pa->hi_addr, pa->fib_index, data))
      break;
  }
}

int
arp_proxy_disable (u32 sw_if_index)
{
  arp_proxy_main_t *am = &arp_proxy_main;

  vec_validate (am->enabled_by_sw_if_index, sw_if_index);

  if (am->enabled_by_sw_if_index[sw_if_index])
    {
      vnet_feature_enable_disable ("arp", "arp-proxy",
				   sw_if_index, 0, NULL, 0);
    }
  am->enabled_by_sw_if_index[sw_if_index] = false;

  return (0);
}

int
arp_proxy_enable (u32 sw_if_index)
{
  arp_proxy_main_t *am = &arp_proxy_main;

  vec_validate (am->enabled_by_sw_if_index, sw_if_index);

  if (!am->enabled_by_sw_if_index[sw_if_index])
    {
      vnet_feature_enable_disable ("arp", "arp-proxy",
				   sw_if_index, 1, NULL, 0);
    }
  am->enabled_by_sw_if_index[sw_if_index] = true;

  return (0);
}

static int
vnet_proxy_arp_add_del (const ip4_address_t * lo_addr,
			const ip4_address_t * hi_addr,
			u32 fib_index, int is_del)
{
  arp_proxy_main_t *am = &arp_proxy_main;
  ethernet_proxy_arp_t *pa;
  u32 found_at_index = ~0;

  vec_foreach (pa, am->proxy_arps)
  {
    if (pa->lo_addr.as_u32 == lo_addr->as_u32 &&
	pa->hi_addr.as_u32 == hi_addr->as_u32 && pa->fib_index == fib_index)
      {
	found_at_index = pa - am->proxy_arps;
	break;
      }
  }

  if (found_at_index != ~0)
    {
      /* Delete, otherwise it's already in the table */
      if (is_del)
	vec_delete (am->proxy_arps, 1, found_at_index);
      return 0;
    }
  /* delete, no such entry */
  if (is_del)
    return VNET_API_ERROR_NO_SUCH_ENTRY;

  /* add, not in table */
  vec_add2 (am->proxy_arps, pa, 1);
  pa->lo_addr.as_u32 = lo_addr->as_u32;
  pa->hi_addr.as_u32 = hi_addr->as_u32;
  pa->fib_index = fib_index;
  return 0;
}

int
arp_proxy_add (u32 fib_index,
	       const ip4_address_t * lo, const ip4_address_t * hi)
{
  return (vnet_proxy_arp_add_del (lo, hi, fib_index, 0));
}

int
arp_proxy_del (u32 fib_index,
	       const ip4_address_t * lo, const ip4_address_t * hi)
{
  return (vnet_proxy_arp_add_del (lo, hi, fib_index, 1));
}

void
proxy_arp_intfc_walk (proxy_arp_intf_walk_t cb, void *data)
{
  arp_proxy_main_t *am = &arp_proxy_main;
  bool *enabled;

  vec_foreach (enabled, am->enabled_by_sw_if_index)
  {
    if (*enabled)
      cb (enabled - am->enabled_by_sw_if_index, data);
  }
}

static clib_error_t *
set_int_proxy_arp_command_fn (vlib_main_t * vm,
			      unformat_input_t * input,
			      vlib_cli_command_t * cmd)
{
  vnet_main_t *vnm = vnet_get_main ();
  u32 sw_if_index;
  int enable = 0;

  sw_if_index = ~0;

  while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
    {
      if (unformat (input, "%U", unformat_vnet_sw_interface,
		    vnm, &sw_if_index))
	;
      else if (unformat (input, "enable") || unformat (input, "on"))
	enable = 1;
      else if (unformat (input, "disable") || unformat (input, "off"))
	enable = 0;
      else
	break;
    }

  if (~0 == sw_if_index)
    return clib_error_return (0, "unknown input '%U'",
			      format_unformat_error, input);

  if (enable)
    arp_proxy_enable (sw_if_index);
  else
    arp_proxy_disable (sw_if_index);

  return 0;
}

static clib_error_t *
set_arp_proxy (vlib_main_t * vm,
	       unformat_input_t * input, vlib_cli_command_t * cmd)
{
  ip4_address_t start = {.as_u32 = ~0 }, end =
  {
  .as_u32 = ~0};
  u32 fib_index, table_id = 0;
  int add = 1;

  while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
    {
      if (unformat (input, "table-id %d", &table_id))
	;
      else if (unformat (input, "start %U", unformat_ip4_address, &start))
	;
      else if (unformat (input, "end %U", unformat_ip4_address, &end))
	;
      else if (unformat (input, "del") || unformat (input, "delete"))
	add = 0;
      else
	break;
    }

  fib_index = fib_table_find (FIB_PROTOCOL_IP4, table_id);

  if (~0 == fib_index)
    return (clib_error_return (0, "no such table: %d", table_id));

  if (add)
    arp_proxy_add (fib_index, &start, &end);
  else
    arp_proxy_del (fib_index, &start, &end);

  return (NULL);
}

/* *INDENT-OFF* */
/*?
 * Enable proxy-arp on an interface. The vpp stack will answer ARP
 * requests for the indicated address range. Multiple proxy-arp
 * ranges may be provisioned.
 *
 * @note Proxy ARP as a technology is infamous for blackholing traffic.
 * Also, the underlying implementation has not been performance-tuned.
 * Avoid creating an unnecessarily large set of ranges.
 *
 * @cliexpar
 * To enable proxy arp on a range of addresses, use:
 * @cliexcmd{set ip arp proxy 6.0.0.1 - 6.0.0.11}
 * Append 'del' to delete a range of proxy ARP addresses:
 * @cliexcmd{set ip arp proxy 6.0.0.1 - 6.0.0.11 del}
 * You must then specifically enable proxy arp on individual interfaces:
 * @cliexcmd{set interface proxy-arp GigabitEthernet0/8/0 enable}
 * To disable proxy arp on an individual interface:
 * @cliexcmd{set interface proxy-arp GigabitEthernet0/8/0 disable}
 ?*/
VLIB_CLI_COMMAND (set_int_proxy_enable_command, static) = {
  .path = "set interface proxy-arp",
  .short_help =
  "set interface proxy-arp <intfc> [enable|disable]",
  .function = set_int_proxy_arp_command_fn,
};
/* *INDENT-ON* */

/* *INDENT-OFF* */
VLIB_CLI_COMMAND (set_arp_proxy_command, static) = {
  .path = "set arp proxy",
  .short_help = "set arp proxy [del] table-ID <table-ID> start <start-address> end <end-addres>",
  .function = set_arp_proxy,
};
/* *INDENT-ON* */

typedef struct
{
  u8 packet_data[64];
} ethernet_arp_input_trace_t;

static u8 *
format_ethernet_arp_input_trace (u8 * s, va_list * va)
{
  CLIB_UNUSED (vlib_main_t * vm) = va_arg (*va, vlib_main_t *);
  CLIB_UNUSED (vlib_node_t * node) = va_arg (*va, vlib_node_t *);
  ethernet_arp_input_trace_t *t = va_arg (*va, ethernet_arp_input_trace_t *);

  s = format (s, "%U",
	      format_ethernet_arp_header,
	      t->packet_data, sizeof (t->packet_data));

  return s;
}

static uword
arp_proxy (vlib_main_t * vm, vlib_node_runtime_t * node, vlib_frame_t * frame)
{
  arp_proxy_main_t *am = &arp_proxy_main;
  vnet_main_t *vnm = vnet_get_main ();
  u32 n_left_from, next_index, *from, *to_next;
  u32 n_arp_replies_sent = 0;

  from = vlib_frame_vector_args (frame);
  n_left_from = frame->n_vectors;
  next_index = node->cached_next_index;

  if (node->flags & VLIB_NODE_FLAG_TRACE)
    vlib_trace_frame_buffers_only (vm, node, from, frame->n_vectors,
				   /* stride */ 1,
				   sizeof (ethernet_arp_input_trace_t));

  while (n_left_from > 0)
    {
      u32 n_left_to_next;

      vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next);

      while (n_left_from > 0 && n_left_to_next > 0)
	{
	  vlib_buffer_t *p0;
	  ethernet_arp_header_t *arp0;
	  ethernet_header_t *eth_rx;
	  ip4_address_t proxy_src;
	  u32 pi0, error0, next0, sw_if_index0, fib_index0;
	  u8 is_request0;
	  ethernet_proxy_arp_t *pa;

	  pi0 = from[0];
	  to_next[0] = pi0;
	  from += 1;
	  to_next += 1;
	  n_left_from -= 1;
	  n_left_to_next -= 1;

	  p0 = vlib_get_buffer (vm, pi0);
	  arp0 = vlib_buffer_get_current (p0);
	  /* Fill in ethernet header. */
	  eth_rx = ethernet_buffer_get_header (p0);

	  is_request0 = arp0->opcode
	    == clib_host_to_net_u16 (ETHERNET_ARP_OPCODE_request);

	  error0 = ETHERNET_ARP_ERROR_replies_sent;
	  sw_if_index0 = vnet_buffer (p0)->sw_if_index[VLIB_RX];
	  next0 = ARP_REPLY_NEXT_DROP;

	  fib_index0 = ip4_fib_table_get_index_for_sw_if_index (sw_if_index0);
	  if (~0 == fib_index0)
	    {
	      error0 = ETHERNET_ARP_ERROR_interface_no_table;
	    }

	  if (0 == error0 && is_request0)
	    {
	      u32 this_addr = clib_net_to_host_u32
		(arp0->ip4_over_ethernet[1].ip4.as_u32);

	      vec_foreach (pa, am->proxy_arps)
	      {
		u32 lo_addr = clib_net_to_host_u32 (pa->lo_addr.as_u32);
		u32 hi_addr = clib_net_to_host_u32 (pa->hi_addr.as_u32);

		/* an ARP request hit in the proxy-arp table? */
		if ((this_addr >= lo_addr && this_addr <= hi_addr) &&
		    (fib_index0 == pa->fib_index))
		  {
		    proxy_src.as_u32 =
		      arp0->ip4_over_ethernet[1].ip4.data_u32;

		    /*
		     * change the interface address to the proxied
		     */
		    n_arp_replies_sent++;

		    next0 =
		      arp_mk_reply (vnm, p0, sw_if_index0, &proxy_src, arp0,
				    eth_rx);
		  }
	      }
	    }
	  else
	    {
	      p0->error = node->errors[error0];
	    }

	  vlib_validate_buffer_enqueue_x1 (vm, node, next_index, to_next,
					   n_left_to_next, pi0, next0);
	}

      vlib_put_next_frame (vm, node, next_index, n_left_to_next);
    }

  vlib_error_count (vm, node->node_index,
		    ETHERNET_ARP_ERROR_replies_sent, n_arp_replies_sent);

  return frame->n_vectors;
}

static char *ethernet_arp_error_strings[] = {
#define _(sym,string) string,
  foreach_ethernet_arp_error
#undef _
};

VLIB_REGISTER_NODE (arp_proxy_node, static) =
{
  .function = arp_proxy,.name = "arp-proxy",.vector_size =
    sizeof (u32),.n_errors = ETHERNET_ARP_N_ERROR,.error_strings =
    ethernet_arp_error_strings,.n_next_nodes = ARP_REPLY_N_NEXT,.next_nodes =
  {
  [ARP_REPLY_NEXT_DROP] = "error-drop",
      [ARP_REPLY_NEXT_REPLY_TX] = "interface-output",}
,.format_buffer = format_ethernet_arp_header,.format_trace =
    format_ethernet_arp_input_trace,};

static clib_error_t *
show_ip4_arp (vlib_main_t * vm,
	      unformat_input_t * input, vlib_cli_command_t * cmd)
{
  arp_proxy_main_t *am = &arp_proxy_main;
  ethernet_proxy_arp_t *pa;

  if (vec_len (am->proxy_arps))
    {
      vlib_cli_output (vm, "Proxy arps enabled for:");
      vec_foreach (pa, am->proxy_arps)
      {
	vlib_cli_output (vm, "Fib_index %d   %U - %U ",
			 pa->fib_index,
			 format_ip4_address, &pa->lo_addr,
			 format_ip4_address, &pa->hi_addr);
      }
    }

  return (NULL);
}

/*?
 * Display all the IPv4 ARP proxy entries.
 *
 * @cliexpar
 * Example of how to display the IPv4 ARP table:
 * @cliexstart{show ip arp}
 *    Time      FIB        IP4       Flags      Ethernet              Interface
 *    346.3028   0       6.1.1.3            de:ad:be:ef:ba:be   GigabitEthernet2/0/0
 *   3077.4271   0       6.1.1.4       S    de:ad:be:ef:ff:ff   GigabitEthernet2/0/0
 *   2998.6409   1       6.2.2.3            de:ad:be:ef:00:01   GigabitEthernet2/0/0
 * Proxy arps enabled for:
 * Fib_index 0   6.0.0.1 - 6.0.0.11
 * @cliexend
 ?*/
/* *INDENT-OFF* */
VLIB_CLI_COMMAND (show_ip4_arp_command, static) = {
  .path = "show arp proxy",
  .function = show_ip4_arp,
  .short_help = "show ip arp",
};
/* *INDENT-ON* */

/*
 * fd.io coding-style-patch-verification: ON
 *
 * Local Variables:
 * eval: (c-set-style "gnu")
 * End:
 */