aboutsummaryrefslogtreecommitdiffstats
path: root/src/vnet/srv6
AgeCommit message (Collapse)AuthorFilesLines
2019-07-28sr: ipv6 segment routing header (srh) updateAhmed Abdelsalam4-1581/+18
SRH has passed WG review in IETF and currently an IESG document. This patch updates the SRH definition to be compliant with IETF. - Change "first_segment" to "last_entry" - Change "reserved" to "tag" Change-Id: I1765c968671655c5646f6de478d1f7196abbc040 Type: fix Signed-off-by: Ahmed Abdelsalam <ahabdels@cisco.com>
2019-07-28sr: fix srv6 end.t behaviorAhmed Abdelsalam1-1/+2
Update the sr_localsid code to set the DPO for SRv6 End.T behavior. Change-Id: I17e102a419eb2b4fdd1de7a672b109e4ff7e1dc2 Type: fix Signed-off-by: Ahmed Abdelsalam <ahabdels@cisco.com>
2019-04-08fixing typosJim Thompson1-1/+1
Change-Id: I215e1e0208a073db80ec6f87695d734cf40fabe3 Signed-off-by: Jim Thompson <jim@netgate.com>
2018-11-14Remove c-11 memcpy checks from perf-critical codeDave Barach3-213/+216
Change-Id: Id4f37f5d4a03160572954a416efa1ef9b3d79ad1 Signed-off-by: Dave Barach <dave@barachs.net>
2018-10-23c11 safe string handling supportDave Barach4-14/+15
Change-Id: Ied34720ca5a6e6e717eea4e86003e854031b6eab Signed-off-by: Dave Barach <dave@barachs.net>
2018-09-14Fix memory leak in API/CLI to create/modify SR policiesJohn Lo2-0/+4
The segment list vector built by the API/CLI is not freed after SR policy creation or modification. Change-Id: If439005481cada6c6af7cb560fe7a4381dd49384 Signed-off-by: John Lo <loj@cisco.com>
2018-07-12srv6: fix pointers in quad loopsFrancois Clad1-6/+6
Change-Id: I51c4bcbd456d2e7ffbdeef9cc8c7703d1c88b52e Signed-off-by: Francois Clad <fclad@cisco.com>
2018-07-11avoid using thread local storage for thread indexDamjan Marion1-2/+2
It is cheaper to get thread index from vlib_main_t if available... Change-Id: I4582e160d06d9d7fccdc54271912f0635da79b50 Signed-off-by: Damjan Marion <damarion@cisco.com>
2018-06-19Fixed bugs in SRv6 APIPablo Camarillo3-56/+216
Jira ticket VPP-1196 Jira ticket VPP-1081 Jira ticket VPP-1078 Jira ticket VPP-1217 Change-Id: Id7e85229cae1017acb0aa4ca63ced334e6dafb8d Signed-off-by: pcamaril <pcamaril@cisco.com> Signed-off-by: Pablo Camarillo <pcamaril@cisco.com> Signed-off-by: pcamaril <pcamaril@cisco.com> Signed-off-by: Michal Cmarada <michal.cmarada@pantheon.tech>
2018-04-25Fix some build warnings about "Old Style VLA"Juraj Sloboda1-2/+7
Change-Id: I69fee1dcf07a4d2eed69a59f0a36e63e3741ed4e Signed-off-by: Juraj Sloboda <jsloboda@cisco.com>
2018-03-14srv6-plugins: fixing documentationFrancois Clad1-0/+7
Change-Id: I72439df585e56b3cbb7051f056fc35cddf0c864b Signed-off-by: Francois Clad <fclad@cisco.com>
2018-03-14srv6: minor CLI modificationsFrancois Clad4-15/+14
'sr steer [...] via sr policy [...]' -> 'sr steer [...] via [...]' 'show sr steering policies' -> 'show sr steering-policies' 'clear sr localsid counters' -> 'clear sr localsid-counters' Change-Id: I0efbf38753a4bd0b7fba8cd08250e136e8e55199 Signed-off-by: Francois Clad <fclad@cisco.com>
2018-02-27SRv6: API uses table-IDs not FIB indiciesNeale Ranns1-3/+3
Change-Id: Iacbf7593ef9c03000db502e68335ac3ae18995d7 Signed-off-by: Neale Ranns <nranns@cisco.com>
2018-01-23VPPAPIGEN: vppapigen replacement in Python PLY.Ole Troan1-1/+1
This is a version of the VPP API generator in Python PLY. It supports the existing language, and has a plugin architecture for generators. Currently C and JSON are supported. Changes: - vl_api_version to option version = "major.minor.patch" - enum support - Added error checking and reporting - import support (removed the C pre-processor) - services (tying request/reply together) Version: option version = "1.0.0"; Enum: enum colours { RED, BLUE = 50, }; define foo { vl_api_colours_t colours; }; Services: service { rpc foo returns foo_reply; rpc foo_dump returns stream foo_details; rpc want_stats returns want_stats_reply events ip4_counters, ip6_counters; }; Future planned features: - unions - bool, text - array support (including length) - proto3 output plugin - Refactor C/C++ generator as a plugin - Refactor Java generator as a plugin Change-Id: Ifa289966c790e1b1a8e2938a91e69331e3a58bdf Signed-off-by: Ole Troan <ot@cisco.com>
2018-01-11api: remove transport specific code from handlersFlorin Coras1-9/+7
This does not update api client code. In other words, if the client assumes the transport is shmem based, this patch does not change that. Furthermore, code that checks queue size, for tail dropping, is not updated. Done for the following apis: Plugins - acl - gtpu - memif - nat - pppoe VNET - bfd - bier - tapv2 - vhost user - dhcp - flow - geneve - ip - punt - ipsec/ipsec-gre - l2 - l2tp - lisp-cp/one-cp - lisp-gpe - map - mpls - policer - session - span - udp - tap - vxlan/vxlan-gpe - interface VPP - api/api.c OAM - oam_api.c Stats - stats.c Change-Id: I0e33ecefb2bdab0295698c0add948068a5a83345 Signed-off-by: Florin Coras <fcoras@cisco.com>
2018-01-09VPP-1120 SRv6 bug with SID list containing only one SID and no srhPablo Camarillo1-26/+64
Packets with a SR policy of one SID list create a malformed packet. Outer IPv6 header points to a routing extension header that does not exist since the SID list contains only one SID. Change-Id: I1effee0457453bab95706a8a24fdabfbd843d5b7 Signed-off-by: Pablo Camarillo <pcamaril@cisco.com>
2018-01-09api: refactor vlibmemoryFlorin Coras1-2/+2
- separate client/server code for both memory and socket apis - separate memory api code from generic vlib api code - move unix_shared_memory_fifo to svm and rename to svm_fifo_t - overall declutter Change-Id: I90cdd98ff74d0787d58825b914b0f1eafcfa4dc2 Signed-off-by: Florin Coras <fcoras@cisco.com>
2017-11-27SRv6 improvements to binary APIPablo Camarillo4-22/+84
Change-Id: I260f5fe43074c0be973309f0a0895f50f0ca7c2b Signed-off-by: Pablo Camarillo <pcamaril@cisco.com>
2017-11-10add classify session action set-sr-policy-indexGabriel Ganne1-0/+24
This allows to use the classifier to steer source routing packets instead of using the "sr steer" command. This way we can steer on anything instead of only the dst ip address. test: * add add_node_next function to the VppPapiProvider class. * add simple test scenario using the classifier to steer packets with dest ip addr == a7::/8 to the source routing insert node. * use new interface indexes (3,4) instead of (0,1) to prevent a cleanup conflict with the other tests which attach a specific fib to the interface. The test creates interfaces sepsrated from the other tests to prevent a conflict in the cleaning of the ip6 fib index 1 which causes vpp not to be able to find a default route on this table. Change-Id: Ibacb30fab3ce53f0dfe848ca6a8cdf0d111d8336 Signed-off-by: Gabriel Ganne <gabriel.ganne@enea.com>
2017-11-09BIERNeale Ranns1-1/+1
- see draft-ietf-bier-mpls-encapsulation-10 - midpoint, head and tail functions - supported payload protocols; IPv4 and IPv6 only. Change-Id: I59d7363bb6fdfdce8e4016a68a9c8f5a5e5791cb Signed-off-by: Neale Ranns <nranns@cisco.com>
2017-10-09vppapigen: support per-file (major,minor,patch) version stampsDave Barach1-2/+6
Add one of these statements to foo.api: vl_api_version 1.2.3 to generate a version tuple stanza in foo.api.h: /****** Version tuple *****/ vl_api_version_tuple(foo, 1, 2, 3) Change-Id: Ic514439e4677999daa8463a94f948f76b132ff15 Signed-off-by: Dave Barach <dave@barachs.net> Signed-off-by: Ole Troan <ot@cisco.com>
2017-09-27Various fixes for issues found by Coverity (VPP-972)Chris Luke1-0/+10
174267: Revisit this string termination issue 174816: Add check for NULL when trace is enabled 177211: Add notation that mutex is not required here 177117: Added check for log2_page_size == 0 and returns an error if so 163697,163698: Added missing sw_if_index validation Change-Id: I5a76fcf6505c785bfb3269e353360031c6a0fd0f Signed-off-by: Chris Luke <chrisy@flirble.org>
2017-09-11FIB table add/delete APINeale Ranns2-4/+8
part 2; - this adds the code to create an IP and MPLS table via the API. - but the enforcement that the table must be created before it is used is still missing, this is so that CSIT can pass. Change-Id: Id124d884ade6cb7da947225200e3bb193454c555 Signed-off-by: Neale Ranns <nranns@cisco.com>
2017-08-22SRv6 testsKris Michielsen3-32/+201
Change-Id: Ib1d2fc5a83d9d007a0468591a73881675f1bec9b Signed-off-by: Kris Michielsen <kmichiel@cisco.com>
2017-08-08L2 over MPLSNeale Ranns1-2/+2
[support for VPWS/VPLS] - switch to using dpo_proto_t rather than fib_protocol_t in fib_paths so that we can describe L2 paths - VLIB nodes to handle pop/push of MPLS labels to L2 Change-Id: Id050d06a11fd2c9c1c81ce5a0654e6c5ae6afa6e Signed-off-by: Neale Ranns <nranns@cisco.com>
2017-06-07VPP-872 and End.T function for SRv6Pablo Camarillo2-105/+101
Fixes VPP-872 and adds support for End.T Change-Id: I3c32cb6e412f37babe1abd293c0b6b49367fc2a9 Signed-off-by: Pablo Camarillo <pcamaril@cisco.com>
2017-05-05First commit SR MPLSPablo Camarillo14-0/+8014
Change-Id: I961685a2a0e4c314049444c64eb6ccf877c278dd Signed-off-by: Pablo Camarillo <pcamaril@cisco.com>
id='n836' href='#n836'>836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699
/*
 * Copyright (c) 2016 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 <stddef.h>

#include <vlib/vlib.h>
#include <vlib/unix/unix.h>
#include <vnet/fib/ip6_fib.h>
#include <vnet/fib/ip4_fib.h>
#include <vnet/ip/ip_sas.h>
#include <vnet/ip/ip6_link.h>
#include <vnet/ip/ip6_ll_table.h>
#include <vnet/plugin/plugin.h>
#include <vpp/app/version.h>

#include <vnet/ip/icmp4.h>
#include <ping/ping.h>

ping_main_t ping_main;

/**
 * @file
 * @brief IPv4 and IPv6 ICMP Ping.
 *
 * This file contains code to support IPv4 or IPv6 ICMP ECHO_REQUEST to
 * network hosts.
 *
 */

typedef struct
{
  u16 id;
  u16 seq;
  u32 cli_process_node;
  u8 is_ip6;
} icmp_echo_trace_t;


u8 *
format_icmp_echo_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 *);
  icmp_echo_trace_t *t = va_arg (*va, icmp_echo_trace_t *);

  s =
    format (s, "ICMP%s echo id %d seq %d", t->is_ip6 ? "6" : "4", t->id,
	    t->seq);
  if (t->cli_process_node == PING_CLI_UNKNOWN_NODE)
    {
      s = format (s, " (unknown)");
    }
  else
    {
      s = format (s, " send to cli node %d", t->cli_process_node);
    }

  return s;
}


static u8 *
format_ip46_ping_result (u8 * s, va_list * args)
{
  send_ip46_ping_result_t res = va_arg (*args, send_ip46_ping_result_t);

  switch (res)
    {
#define _(v, n) case SEND_PING_##v: s = format(s, "%s", n);break;
      foreach_ip46_ping_result
#undef _
    }

  return (s);
}


/*
 * Poor man's get-set-clear functions
 * for manipulation of icmp_id -> cli_process_id
 * mappings.
 *
 * There should normally be very few (0..1..2) of these
 * mappings, so the linear search is a good strategy.
 *
 * Make them thread-safe via a simple spinlock.
 *
 */


static_always_inline uword
get_cli_process_id_by_icmp_id_mt (vlib_main_t * vm, u16 icmp_id)
{
  ping_main_t *pm = &ping_main;
  uword cli_process_id = PING_CLI_UNKNOWN_NODE;
  ping_run_t *pr;

  clib_spinlock_lock_if_init (&pm->ping_run_check_lock);
  vec_foreach (pr, pm->active_ping_runs)
  {
    if (pr->icmp_id == icmp_id)
      {
	cli_process_id = pr->cli_process_id;
	break;
      }
  }
  clib_spinlock_unlock_if_init (&pm->ping_run_check_lock);
  return cli_process_id;
}


static_always_inline void
set_cli_process_id_by_icmp_id_mt (vlib_main_t * vm, u16 icmp_id,
				  uword cli_process_id)
{
  ping_main_t *pm = &ping_main;
  ping_run_t *pr;

  clib_spinlock_lock_if_init (&pm->ping_run_check_lock);
  vec_foreach (pr, pm->active_ping_runs)
  {
    if (pr->icmp_id == icmp_id)
      {
	pr->cli_process_id = cli_process_id;
	goto have_found_and_set;
      }
  }
  /* no such key yet - add a new one */
  ping_run_t new_pr = {.icmp_id = icmp_id,.cli_process_id = cli_process_id };
  vec_add1 (pm->active_ping_runs, new_pr);
have_found_and_set:
  clib_spinlock_unlock_if_init (&pm->ping_run_check_lock);
}


static_always_inline void
clear_cli_process_id_by_icmp_id_mt (vlib_main_t * vm, u16 icmp_id)
{
  ping_main_t *pm = &ping_main;
  ping_run_t *pr;

  clib_spinlock_lock_if_init (&pm->ping_run_check_lock);
  vec_foreach (pr, pm->active_ping_runs)
  {
    if (pr->icmp_id == icmp_id)
      {
	vec_del1 (pm->active_ping_runs, pr - pm->active_ping_runs);
	break;
      }
  }
  clib_spinlock_unlock_if_init (&pm->ping_run_check_lock);
}

static_always_inline int
ip46_get_icmp_id_and_seq (vlib_main_t * vm, vlib_buffer_t * b0,
			  u16 * out_icmp_id, u16 * out_icmp_seq, int is_ip6)
{
  int l4_offset;
  if (is_ip6)
    {
      ip6_header_t *ip6 = vlib_buffer_get_current (b0);
      if (ip6->protocol != IP_PROTOCOL_ICMP6)
	{
	  return 0;
	}
      l4_offset = sizeof (*ip6);	// IPv6 EH
    }
  else
    {
      ip4_header_t *ip4 = vlib_buffer_get_current (b0);
      l4_offset = ip4_header_bytes (ip4);

    }
  icmp46_header_t *icmp46 = vlib_buffer_get_current (b0) + l4_offset;
  icmp46_echo_request_t *icmp46_echo = (icmp46_echo_request_t *) (icmp46 + 1);

  *out_icmp_id = clib_net_to_host_u16 (icmp46_echo->id);
  *out_icmp_seq = clib_net_to_host_u16 (icmp46_echo->seq);
  return 1;
}

/*
 * post the buffer to a given cli process node - the caller should forget bi0 after return.
 */

static_always_inline void
ip46_post_icmp_reply_event (vlib_main_t * vm, uword cli_process_id, u32 bi0,
			    int is_ip6)
{
  vlib_buffer_t *b0 = vlib_get_buffer (vm, bi0);
  u64 nowts = clib_cpu_time_now ();

  /* Pass the timestamp to the cli_process thanks to the vnet_buffer unused metadata field */

  /* Camping on unused data... just ensure statically that there is enough space */
  STATIC_ASSERT (ARRAY_LEN (vnet_buffer (b0)->unused) *
		 sizeof (vnet_buffer (b0)->unused[0]) > sizeof (nowts),
		 "ping reply timestamp fits within remaining space of vnet_buffer unused data");
  u64 *pnowts = (void *) &vnet_buffer (b0)->unused[0];
  *pnowts = nowts;

  u32 event_id = is_ip6 ? PING_RESPONSE_IP6 : PING_RESPONSE_IP4;
  vlib_process_signal_event_mt (vm, cli_process_id, event_id, bi0);
}


static_always_inline void
ip46_echo_reply_maybe_trace_buffer (vlib_main_t * vm,
				    vlib_node_runtime_t * node,
				    uword cli_process_id, u16 id, u16 seq,
				    vlib_buffer_t * b0, int is_ip6)
{
  if (PREDICT_FALSE (b0->flags & VLIB_BUFFER_IS_TRACED))
    {
      icmp_echo_trace_t *tr = vlib_add_trace (vm, node, b0, sizeof (*tr));
      tr->id = id;
      tr->seq = seq;
      tr->cli_process_node = cli_process_id;
      tr->is_ip6 = is_ip6;
    }
}


static_always_inline uword
ip46_icmp_echo_reply_inner_node_fn (vlib_main_t * vm,
				    vlib_node_runtime_t * node,
				    vlib_frame_t * frame, int do_trace,
				    int is_ip6)
{
  u32 n_left_from, *from, *to_next;
  icmp46_echo_reply_next_t next_index;

  from = vlib_frame_vector_args (frame);
  n_left_from = frame->n_vectors;

  next_index = node->cached_next_index;

  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)
	{
	  u32 bi0;
	  vlib_buffer_t *b0;
	  /*
	   * The buffers (replies) are either posted to the CLI thread
	   * awaiting for them for subsequent analysis and disposal,
	   * or are sent to the punt node.
	   *
	   * So the only "next" node is a punt, normally.
	   */
	  u32 next0 = ICMP46_ECHO_REPLY_NEXT_PUNT;

	  bi0 = from[0];
	  b0 = vlib_get_buffer (vm, bi0);
	  from += 1;
	  n_left_from -= 1;

	  u16 icmp_id = ~0;
	  u16 icmp_seq = ~0;
	  uword cli_process_id = PING_CLI_UNKNOWN_NODE;

	  if (ip46_get_icmp_id_and_seq (vm, b0, &icmp_id, &icmp_seq, is_ip6))
	    {
	      cli_process_id = get_cli_process_id_by_icmp_id_mt (vm, icmp_id);
	    }

	  if (do_trace)
	    ip46_echo_reply_maybe_trace_buffer (vm, node, cli_process_id,
						icmp_id, icmp_seq, b0,
						is_ip6);

	  if (~0 == cli_process_id)
	    {
	      /* no outstanding requests for this reply, punt */
	      /* speculatively enqueue b0 to the current next frame */
	      to_next[0] = bi0;
	      to_next += 1;
	      n_left_to_next -= 1;
	      /* verify speculative enqueue, maybe switch current next frame */
	      vlib_validate_buffer_enqueue_x1 (vm, node, next_index,
					       to_next, n_left_to_next,
					       bi0, next0);
	    }
	  else
	    {
	      /* Post the buffer to CLI thread. It will take care of freeing it. */
	      ip46_post_icmp_reply_event (vm, cli_process_id, bi0, is_ip6);
	    }
	}
      vlib_put_next_frame (vm, node, next_index, n_left_to_next);
    }
  return frame->n_vectors;
}

/*
 * select "with-trace" or "without-trace" codepaths upfront.
 */
static_always_inline uword
ip46_icmp_echo_reply_outer_node_fn (vlib_main_t * vm,
				    vlib_node_runtime_t * node,
				    vlib_frame_t * frame, int is_ip6)
{
  if (node->flags & VLIB_NODE_FLAG_TRACE)
    return ip46_icmp_echo_reply_inner_node_fn (vm, node, frame,
					       1 /* do_trace */ , is_ip6);
  else
    return ip46_icmp_echo_reply_inner_node_fn (vm, node, frame,
					       0 /* do_trace */ , is_ip6);
}

static uword
ip4_icmp_echo_reply_node_fn (vlib_main_t * vm,
			     vlib_node_runtime_t * node, vlib_frame_t * frame)
{
  return ip46_icmp_echo_reply_outer_node_fn (vm, node, frame,
					     0 /* is_ip6 */ );
}

static uword
ip6_icmp_echo_reply_node_fn (vlib_main_t * vm,
			     vlib_node_runtime_t * node, vlib_frame_t * frame)
{
  return ip46_icmp_echo_reply_outer_node_fn (vm, node, frame,
					     1 /* is_ip6 */ );
}

/* *INDENT-OFF* */
VLIB_REGISTER_NODE (ip6_icmp_echo_reply_node, static) =
{
  .function = ip6_icmp_echo_reply_node_fn,
  .name = "ip6-icmp-echo-reply",
  .vector_size = sizeof (u32),
  .format_trace = format_icmp_echo_trace,
  .n_next_nodes = ICMP46_ECHO_REPLY_N_NEXT,
  .next_nodes = {
    [ICMP46_ECHO_REPLY_NEXT_DROP] = "ip6-drop",
    [ICMP46_ECHO_REPLY_NEXT_PUNT] = "ip6-punt",
  },
};

VLIB_REGISTER_NODE (ip4_icmp_echo_reply_node, static) =
{
  .function = ip4_icmp_echo_reply_node_fn,
  .name = "ip4-icmp-echo-reply",
  .vector_size = sizeof (u32),
  .format_trace = format_icmp_echo_trace,
  .n_next_nodes = ICMP46_ECHO_REPLY_N_NEXT,
  .next_nodes = {
    [ICMP46_ECHO_REPLY_NEXT_DROP] = "ip4-drop",
    [ICMP46_ECHO_REPLY_NEXT_PUNT] = "ip4-punt",
  },
};
/* *INDENT-ON* */

static uword
ip4_icmp_echo_request (vlib_main_t * vm,
		       vlib_node_runtime_t * node, vlib_frame_t * frame)
{
  uword n_packets = frame->n_vectors;
  u32 *from, *to_next;
  u32 n_left_from, n_left_to_next, next;
  ip4_main_t *i4m = &ip4_main;
  u16 *fragment_ids, *fid;
  u8 host_config_ttl = i4m->host_config.ttl;

  from = vlib_frame_vector_args (frame);
  n_left_from = n_packets;
  next = 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 (icmp_input_trace_t));

  /* Get random fragment IDs for replies. */
  fid = fragment_ids = clib_random_buffer_get_data (&vm->random_buffer,
						    n_packets *
						    sizeof (fragment_ids[0]));

  while (n_left_from > 0)
    {
      vlib_get_next_frame (vm, node, next, to_next, n_left_to_next);

      while (n_left_from > 2 && n_left_to_next > 2)
	{
	  vlib_buffer_t *p0, *p1;
	  ip4_header_t *ip0, *ip1;
	  icmp46_header_t *icmp0, *icmp1;
	  u32 bi0, src0, dst0;
	  u32 bi1, src1, dst1;
	  ip_csum_t sum0, sum1;

	  bi0 = to_next[0] = from[0];
	  bi1 = to_next[1] = from[1];

	  from += 2;
	  n_left_from -= 2;
	  to_next += 2;
	  n_left_to_next -= 2;

	  p0 = vlib_get_buffer (vm, bi0);
	  p1 = vlib_get_buffer (vm, bi1);
	  ip0 = vlib_buffer_get_current (p0);
	  ip1 = vlib_buffer_get_current (p1);
	  icmp0 = ip4_next_header (ip0);
	  icmp1 = ip4_next_header (ip1);

	  vnet_buffer (p0)->sw_if_index[VLIB_RX] =
	    vnet_main.local_interface_sw_if_index;
	  vnet_buffer (p1)->sw_if_index[VLIB_RX] =
	    vnet_main.local_interface_sw_if_index;

	  /* Update ICMP checksum. */
	  sum0 = icmp0->checksum;
	  sum1 = icmp1->checksum;

	  ASSERT (icmp0->type == ICMP4_echo_request);
	  ASSERT (icmp1->type == ICMP4_echo_request);
	  sum0 = ip_csum_update (sum0, ICMP4_echo_request, ICMP4_echo_reply,
				 icmp46_header_t, type);
	  sum1 = ip_csum_update (sum1, ICMP4_echo_request, ICMP4_echo_reply,
				 icmp46_header_t, type);
	  icmp0->type = ICMP4_echo_reply;
	  icmp1->type = ICMP4_echo_reply;

	  icmp0->checksum = ip_csum_fold (sum0);
	  icmp1->checksum = ip_csum_fold (sum1);

	  src0 = ip0->src_address.data_u32;
	  src1 = ip1->src_address.data_u32;
	  dst0 = ip0->dst_address.data_u32;
	  dst1 = ip1->dst_address.data_u32;

	  /* Swap source and destination address.
	     Does not change checksum. */
	  ip0->src_address.data_u32 = dst0;
	  ip1->src_address.data_u32 = dst1;
	  ip0->dst_address.data_u32 = src0;
	  ip1->dst_address.data_u32 = src1;

	  /* Update IP checksum. */
	  sum0 = ip0->checksum;
	  sum1 = ip1->checksum;

	  sum0 = ip_csum_update (sum0, ip0->ttl, host_config_ttl,
				 ip4_header_t, ttl);
	  sum1 = ip_csum_update (sum1, ip1->ttl, host_config_ttl,
				 ip4_header_t, ttl);
	  ip0->ttl = host_config_ttl;
	  ip1->ttl = host_config_ttl;

	  /* New fragment id. */
	  sum0 = ip_csum_update (sum0, ip0->fragment_id, fid[0],
				 ip4_header_t, fragment_id);
	  sum1 = ip_csum_update (sum1, ip1->fragment_id, fid[1],
				 ip4_header_t, fragment_id);
	  ip0->fragment_id = fid[0];
	  ip1->fragment_id = fid[1];
	  fid += 2;

	  ip0->checksum = ip_csum_fold (sum0);
	  ip1->checksum = ip_csum_fold (sum1);

	  ASSERT (ip4_header_checksum_is_valid (ip0));
	  ASSERT (ip4_header_checksum_is_valid (ip1));

	  p0->flags |= VNET_BUFFER_F_LOCALLY_ORIGINATED;
	  p1->flags |= VNET_BUFFER_F_LOCALLY_ORIGINATED;
	}

      while (n_left_from > 0 && n_left_to_next > 0)
	{
	  vlib_buffer_t *p0;
	  ip4_header_t *ip0;
	  icmp46_header_t *icmp0;
	  u32 bi0, src0, dst0;
	  ip_csum_t sum0;

	  bi0 = to_next[0] = from[0];

	  from += 1;
	  n_left_from -= 1;
	  to_next += 1;
	  n_left_to_next -= 1;

	  p0 = vlib_get_buffer (vm, bi0);
	  ip0 = vlib_buffer_get_current (p0);
	  icmp0 = ip4_next_header (ip0);

	  vnet_buffer (p0)->sw_if_index[VLIB_RX] =
	    vnet_main.local_interface_sw_if_index;

	  /* Update ICMP checksum. */
	  sum0 = icmp0->checksum;

	  ASSERT (icmp0->type == ICMP4_echo_request);
	  sum0 = ip_csum_update (sum0, ICMP4_echo_request, ICMP4_echo_reply,
				 icmp46_header_t, type);
	  icmp0->type = ICMP4_echo_reply;
	  icmp0->checksum = ip_csum_fold (sum0);

	  src0 = ip0->src_address.data_u32;
	  dst0 = ip0->dst_address.data_u32;
	  ip0->src_address.data_u32 = dst0;
	  ip0->dst_address.data_u32 = src0;

	  /* Update IP checksum. */
	  sum0 = ip0->checksum;

	  sum0 = ip_csum_update (sum0, ip0->ttl, host_config_ttl,
				 ip4_header_t, ttl);
	  ip0->ttl = host_config_ttl;

	  sum0 = ip_csum_update (sum0, ip0->fragment_id, fid[0],
				 ip4_header_t, fragment_id);
	  ip0->fragment_id = fid[0];
	  fid += 1;

	  ip0->checksum = ip_csum_fold (sum0);

	  ASSERT (ip4_header_checksum_is_valid (ip0));

	  p0->flags |= VNET_BUFFER_F_LOCALLY_ORIGINATED;
	}

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

  vlib_error_count (vm, ip4_icmp_input_node.index,
		    ICMP4_ERROR_ECHO_REPLIES_SENT, frame->n_vectors);

  return frame->n_vectors;
}

static u8 *
format_icmp_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 *);
  icmp_input_trace_t *t = va_arg (*va, icmp_input_trace_t *);

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

  return s;
}

/* *INDENT-OFF* */
VLIB_REGISTER_NODE (ip4_icmp_echo_request_node,static) = {
  .function = ip4_icmp_echo_request,
  .name = "ip4-icmp-echo-request",

  .vector_size = sizeof (u32),

  .format_trace = format_icmp_input_trace,

  .n_next_nodes = 1,
  .next_nodes = {
    [0] = "ip4-load-balance",
  },
};
/* *INDENT-ON* */

typedef enum
{
  ICMP6_ECHO_REQUEST_NEXT_LOOKUP,
  ICMP6_ECHO_REQUEST_NEXT_OUTPUT,
  ICMP6_ECHO_REQUEST_N_NEXT,
} icmp6_echo_request_next_t;

static uword
ip6_icmp_echo_request (vlib_main_t *vm, vlib_node_runtime_t *node,
		       vlib_frame_t *frame)
{
  u32 *from, *to_next;
  u32 n_left_from, n_left_to_next, next_index;
  ip6_main_t *im = &ip6_main;

  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 (icmp6_input_trace_t));

  while (n_left_from > 0)
    {
      vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next);

      while (n_left_from > 2 && n_left_to_next > 2)
	{
	  vlib_buffer_t *p0, *p1;
	  ip6_header_t *ip0, *ip1;
	  icmp46_header_t *icmp0, *icmp1;
	  ip6_address_t tmp0, tmp1;
	  ip_csum_t sum0, sum1;
	  u32 bi0, bi1;
	  u32 fib_index0, fib_index1;
	  u32 next0 = ICMP6_ECHO_REQUEST_NEXT_LOOKUP;
	  u32 next1 = ICMP6_ECHO_REQUEST_NEXT_LOOKUP;

	  bi0 = to_next[0] = from[0];
	  bi1 = to_next[1] = from[1];

	  from += 2;
	  n_left_from -= 2;
	  to_next += 2;
	  n_left_to_next -= 2;

	  p0 = vlib_get_buffer (vm, bi0);
	  p1 = vlib_get_buffer (vm, bi1);
	  ip0 = vlib_buffer_get_current (p0);
	  ip1 = vlib_buffer_get_current (p1);
	  icmp0 = ip6_next_header (ip0);
	  icmp1 = ip6_next_header (ip1);

	  /* Check icmp type to echo reply and update icmp checksum. */
	  sum0 = icmp0->checksum;
	  sum1 = icmp1->checksum;

	  ASSERT (icmp0->type == ICMP6_echo_request);
	  ASSERT (icmp1->type == ICMP6_echo_request);
	  sum0 = ip_csum_update (sum0, ICMP6_echo_request, ICMP6_echo_reply,
				 icmp46_header_t, type);
	  sum1 = ip_csum_update (sum1, ICMP6_echo_request, ICMP6_echo_reply,
				 icmp46_header_t, type);

	  icmp0->checksum = ip_csum_fold (sum0);
	  icmp1->checksum = ip_csum_fold (sum1);

	  icmp0->type = ICMP6_echo_reply;
	  icmp1->type = ICMP6_echo_reply;

	  /* Swap source and destination address. */
	  tmp0 = ip0->src_address;
	  tmp1 = ip1->src_address;

	  ip0->src_address = ip0->dst_address;
	  ip1->src_address = ip1->dst_address;

	  ip0->dst_address = tmp0;
	  ip1->dst_address = tmp1;

	  /* New hop count. */
	  ip0->hop_limit = im->host_config.ttl;
	  ip1->hop_limit = im->host_config.ttl;

	  if (ip6_address_is_link_local_unicast (&ip0->src_address) &&
	      !ip6_address_is_link_local_unicast (&ip0->dst_address))
	    {
	      fib_index0 = vec_elt (im->fib_index_by_sw_if_index,
				    vnet_buffer (p0)->sw_if_index[VLIB_RX]);
	      vnet_buffer (p0)->sw_if_index[VLIB_TX] = fib_index0;
	    }
	  if (ip6_address_is_link_local_unicast (&ip1->src_address) &&
	      !ip6_address_is_link_local_unicast (&ip1->dst_address))
	    {
	      fib_index1 = vec_elt (im->fib_index_by_sw_if_index,
				    vnet_buffer (p1)->sw_if_index[VLIB_RX]);
	      vnet_buffer (p1)->sw_if_index[VLIB_TX] = fib_index1;
	    }
	  p0->flags |= VNET_BUFFER_F_LOCALLY_ORIGINATED;
	  p1->flags |= VNET_BUFFER_F_LOCALLY_ORIGINATED;

	  /* verify speculative enqueues, maybe switch current next frame */
	  /* if next0==next1==next_index then nothing special needs to be done
	   */
	  vlib_validate_buffer_enqueue_x2 (vm, node, next_index, to_next,
					   n_left_to_next, bi0, bi1, next0,
					   next1);
	}

      while (n_left_from > 0 && n_left_to_next > 0)
	{
	  vlib_buffer_t *p0;
	  ip6_header_t *ip0;
	  icmp46_header_t *icmp0;
	  u32 bi0;
	  ip6_address_t tmp0;
	  ip_csum_t sum0;
	  u32 fib_index0;
	  u32 next0 = ICMP6_ECHO_REQUEST_NEXT_LOOKUP;

	  bi0 = to_next[0] = from[0];

	  from += 1;
	  n_left_from -= 1;
	  to_next += 1;
	  n_left_to_next -= 1;

	  p0 = vlib_get_buffer (vm, bi0);
	  ip0 = vlib_buffer_get_current (p0);
	  icmp0 = ip6_next_header (ip0);

	  /* Check icmp type to echo reply and update icmp checksum. */
	  sum0 = icmp0->checksum;

	  ASSERT (icmp0->type == ICMP6_echo_request);
	  sum0 = ip_csum_update (sum0, ICMP6_echo_request, ICMP6_echo_reply,
				 icmp46_header_t, type);

	  icmp0->checksum = ip_csum_fold (sum0);

	  icmp0->type = ICMP6_echo_reply;

	  /* Swap source and destination address. */
	  tmp0 = ip0->src_address;
	  ip0->src_address = ip0->dst_address;
	  ip0->dst_address = tmp0;

	  ip0->hop_limit = im->host_config.ttl;

	  if (ip6_address_is_link_local_unicast (&ip0->src_address) &&
	      !ip6_address_is_link_local_unicast (&ip0->dst_address))
	    {
	      /* if original packet was to the link local, then the
	       * fib index is that of the LL table, we can't use that
	       * to foward the response if the new destination
	       * is global, so reset to the fib index of the link.
	       * In other case, the fib index we need has been written
	       * to the buffer already. */
	      fib_index0 = vec_elt (im->fib_index_by_sw_if_index,
				    vnet_buffer (p0)->sw_if_index[VLIB_RX]);
	      vnet_buffer (p0)->sw_if_index[VLIB_TX] = fib_index0;
	    }
	  p0->flags |= VNET_BUFFER_F_LOCALLY_ORIGINATED;
	  /* Verify speculative enqueue, maybe switch current next frame */
	  vlib_validate_buffer_enqueue_x1 (vm, node, next_index, to_next,
					   n_left_to_next, bi0, next0);
	}

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

  vlib_error_count (vm, ip6_icmp_input_node.index,
		    ICMP6_ERROR_ECHO_REPLIES_SENT, frame->n_vectors);

  return frame->n_vectors;
}

VLIB_REGISTER_NODE (ip6_icmp_echo_request_node,static) = {
  .function = ip6_icmp_echo_request,
  .name = "ip6-icmp-echo-request",

  .vector_size = sizeof (u32),

  .format_trace = format_icmp6_input_trace,

  .n_next_nodes = ICMP6_ECHO_REQUEST_N_NEXT,
  .next_nodes = {
    [ICMP6_ECHO_REQUEST_NEXT_LOOKUP] = "ip6-lookup",
    [ICMP6_ECHO_REQUEST_NEXT_OUTPUT] = "interface-output",
  },
};

/*
 * A swarm of address-family agnostic helper functions
 * for building and sending the ICMP echo request.
 *
 * Deliberately mostly "static" rather than "static inline"
 * so one can trace them sanely if needed in debugger, if needed.
 *
 */

static_always_inline u8
get_icmp_echo_payload_byte (int offset)
{
  return (offset % 256);
}

/* Fill in the ICMP ECHO structure, return the safety-checked and possibly shrunk data_len */
static u16
init_icmp46_echo_request (vlib_main_t * vm, vlib_buffer_t * b0,
			  int l4_header_offset,
			  icmp46_echo_request_t * icmp46_echo, u16 seq_host,
			  u16 id_host, u64 now, u16 data_len)
{
  int i;


  int l34_len =
    l4_header_offset + sizeof (icmp46_header_t) +
    offsetof (icmp46_echo_request_t, data);
  int max_data_len = vlib_buffer_get_default_data_size (vm) - l34_len;

  int first_buf_data_len = data_len < max_data_len ? data_len : max_data_len;

  int payload_offset = 0;
  for (i = 0; i < first_buf_data_len; i++)
    icmp46_echo->data[i] = get_icmp_echo_payload_byte (payload_offset++);

  /* inspired by vlib_buffer_add_data */
  vlib_buffer_t *hb = b0;
  int remaining_data_len = data_len - first_buf_data_len;
  while (remaining_data_len)
    {
      int this_buf_data_len =
	remaining_data_len <
	vlib_buffer_get_default_data_size (vm) ? remaining_data_len :
	vlib_buffer_get_default_data_size (vm);
      int n_alloc = vlib_buffer_alloc (vm, &b0->next_buffer, 1);
      if (n_alloc < 1)
	{
	  /* That is how much we have so far - return it... */
	  return (data_len - remaining_data_len);
	}
      b0->flags |= VLIB_BUFFER_NEXT_PRESENT;
      /* move on to the newly acquired buffer */
      b0 = vlib_get_buffer (vm, b0->next_buffer);
      /* initialize the data */
      for (i = 0; i < this_buf_data_len; i++)
	{
	  b0->data[i] = get_icmp_echo_payload_byte (payload_offset++);
	}
      b0->current_length = this_buf_data_len;
      b0->current_data = 0;
      remaining_data_len -= this_buf_data_len;
    }
  hb->flags |= VLIB_BUFFER_TOTAL_LENGTH_VALID;
  hb->current_length = l34_len + first_buf_data_len;
  hb->total_length_not_including_first_buffer = data_len - first_buf_data_len;

  icmp46_echo->time_sent = now;
  icmp46_echo->seq = clib_host_to_net_u16 (seq_host);
  icmp46_echo->id = clib_host_to_net_u16 (id_host);
  return data_len;
}


static u32
ip46_fib_index_from_table_id (u32 table_id, int is_ip6)
{
  u32 fib_index = is_ip6 ?
    ip6_fib_index_from_table_id (table_id) :
    ip4_fib_index_from_table_id (table_id);
  return fib_index;
}

static fib_node_index_t
ip46_fib_table_lookup_host (u32 fib_index, ip46_address_t * pa46, int is_ip6)
{
  fib_node_index_t fib_entry_index = is_ip6 ?
    ip6_fib_table_lookup (fib_index, &pa46->ip6, 128) :
    ip4_fib_table_lookup (ip4_fib_get (fib_index), &pa46->ip4, 32);
  return fib_entry_index;
}

static u32
ip46_get_resolving_interface (u32 fib_index, ip46_address_t * pa46,
			      int is_ip6)
{
  u32 sw_if_index = ~0;
  if (~0 != fib_index)
    {
      fib_node_index_t fib_entry_index;
      fib_entry_index = ip46_fib_table_lookup_host (fib_index, pa46, is_ip6);
      sw_if_index = fib_entry_get_resolving_interface (fib_entry_index);
    }
  return sw_if_index;
}

static u32
ip46_fib_table_get_index_for_sw_if_index (u32 sw_if_index, int is_ip6,
					  ip46_address_t *pa46)
{
  if (is_ip6)
    {
      if (ip6_address_is_link_local_unicast (&pa46->ip6))
	return ip6_ll_fib_get (sw_if_index);
      return ip6_fib_table_get_index_for_sw_if_index (sw_if_index);
    }
  return ip4_fib_table_get_index_for_sw_if_index (sw_if_index);
}


static int
ip46_fill_l3_header (ip46_address_t * pa46, vlib_buffer_t * b0, int is_ip6)
{
  if (is_ip6)
    {
      ip6_header_t *ip6 = vlib_buffer_get_current (b0);
      /* Fill in ip6 header fields */
      ip6->ip_version_traffic_class_and_flow_label =
	clib_host_to_net_u32 (0x6 << 28);
      ip6->payload_length = 0;	/* will be set later */
      ip6->protocol = IP_PROTOCOL_ICMP6;
      ip6->hop_limit = 255;
      ip6->dst_address = pa46->ip6;
      ip6->src_address = pa46->ip6;
      return (sizeof (ip6_header_t));
    }
  else
    {
      ip4_header_t *ip4 = vlib_buffer_get_current (b0);
      /* Fill in ip4 header fields */
      ip4->checksum = 0;
      ip4->ip_version_and_header_length = 0x45;
      ip4->tos = 0;
      ip4->length = 0;		/* will be set later */
      ip4->fragment_id = 0;
      ip4->flags_and_fragment_offset = 0;
      ip4->ttl = 0xff;
      ip4->protocol = IP_PROTOCOL_ICMP;
      ip4->src_address = pa46->ip4;
      ip4->dst_address = pa46->ip4;
      return (sizeof (ip4_header_t));
    }
}

static bool
ip46_set_src_address (u32 sw_if_index, vlib_buffer_t * b0, int is_ip6)
{
  bool res = false;

  if (is_ip6)
    {
      ip6_header_t *ip6 = vlib_buffer_get_current (b0);

      res = ip6_sas_by_sw_if_index (sw_if_index, &ip6->dst_address,
				    &ip6->src_address);
    }
  else
    {
      ip4_header_t *ip4 = vlib_buffer_get_current (b0);

      res = ip4_sas_by_sw_if_index (sw_if_index, &ip4->dst_address,
				    &ip4->src_address);
    }
  return res;
}

static void
ip46_print_buffer_src_address (vlib_main_t * vm, vlib_buffer_t * b0,
			       int is_ip6)
{
  void *format_addr_func;
  void *paddr;
  if (is_ip6)
    {
      ip6_header_t *ip6 = vlib_buffer_get_current (b0);
      format_addr_func = format_ip6_address;
      paddr = &ip6->src_address;
    }
  else
    {
      ip4_header_t *ip4 = vlib_buffer_get_current (b0);
      format_addr_func = format_ip4_address;
      paddr = &ip4->src_address;
    }
  vlib_cli_output (vm, "Source address: %U ", format_addr_func, paddr);
}

static u16
ip46_fill_icmp_request_at (vlib_main_t * vm, int l4_offset, u16 seq_host,
			   u16 id_host, u16 data_len, vlib_buffer_t * b0,
			   int is_ip6)
{
  icmp46_header_t *icmp46 = vlib_buffer_get_current (b0) + l4_offset;

  icmp46->type = is_ip6 ? ICMP6_echo_request : ICMP4_echo_request;
  icmp46->code = 0;
  icmp46->checksum = 0;

  icmp46_echo_request_t *icmp46_echo = (icmp46_echo_request_t *) (icmp46 + 1);

  data_len =
    init_icmp46_echo_request (vm, b0, l4_offset, icmp46_echo, seq_host,
			      id_host, clib_cpu_time_now (), data_len);
  return data_len;
}


/* Compute ICMP4 checksum with multibuffer support. */
u16
ip4_icmp_compute_checksum (vlib_main_t * vm, vlib_buffer_t * p0,
			   ip4_header_t * ip0)
{
  ip_csum_t sum0;
  u32 ip_header_length, payload_length_host_byte_order;
  u32 n_this_buffer, n_bytes_left, n_ip_bytes_this_buffer;
  u16 sum16;
  void *data_this_buffer;

  ip_header_length = ip4_header_bytes (ip0);
  payload_length_host_byte_order =
    clib_net_to_host_u16 (ip0->length) - ip_header_length;

  /* ICMP4 checksum does not include the IP header */
  sum0 = 0;

  n_bytes_left = n_this_buffer = payload_length_host_byte_order;
  data_this_buffer = (void *) ip0 + ip_header_length;
  n_ip_bytes_this_buffer =
    p0->current_length - (((u8 *) ip0 - p0->data) - p0->current_data);
  if (n_this_buffer + ip_header_length > n_ip_bytes_this_buffer)
    {
      n_this_buffer = n_ip_bytes_this_buffer > ip_header_length ?
	n_ip_bytes_this_buffer - ip_header_length : 0;
    }
  while (1)
    {
      sum0 = ip_incremental_checksum (sum0, data_this_buffer, n_this_buffer);
      n_bytes_left -= n_this_buffer;
      if (n_bytes_left == 0)
	break;

      ASSERT (p0->flags & VLIB_BUFFER_NEXT_PRESENT);
      p0 = vlib_get_buffer (vm, p0->next_buffer);
      data_this_buffer = vlib_buffer_get_current (p0);
      n_this_buffer = p0->current_length;
    }

  sum16 = ~ip_csum_fold (sum0);

  return sum16;
}


static void
ip46_fix_len_and_csum (vlib_main_t * vm, int l4_offset, u16 data_len,
		       vlib_buffer_t * b0, int is_ip6)
{
  u16 payload_length =
    data_len + sizeof (icmp46_header_t) + offsetof (icmp46_echo_request_t,
						    data);
  u16 total_length = payload_length + l4_offset;
  icmp46_header_t *icmp46 = vlib_buffer_get_current (b0) + l4_offset;
  icmp46->checksum = 0;

  if (is_ip6)
    {
      ip6_header_t *ip6 = vlib_buffer_get_current (b0);
      ip6->payload_length = clib_host_to_net_u16 (payload_length);

      int bogus_length = 0;
      icmp46->checksum =
	ip6_tcp_udp_icmp_compute_checksum (vm, b0, ip6, &bogus_length);
    }
  else
    {
      ip4_header_t *ip4 = vlib_buffer_get_current (b0);
      ip4->length = clib_host_to_net_u16 (total_length);

      ip4->checksum = ip4_header_checksum (ip4);
      icmp46->checksum = ip4_icmp_compute_checksum (vm, b0, ip4);
    }
}

static u16
at_most_a_frame (u32 count)
{
  return count > VLIB_FRAME_SIZE ? VLIB_FRAME_SIZE : count;
}

static int
ip46_enqueue_packet (vlib_main_t *vm, vlib_buffer_t *b0, u32 burst,
		     u32 lookup_node_index)
{
  vlib_frame_t *f = 0;
  int n_sent = 0;

  u16 n_to_send;

  /*
   * Enqueue the packet, possibly as one or more frames of copies to make
   * bursts. We enqueue b0 as the very last buffer, when there is no possibility
   * for error in vlib_buffer_copy, so as to allow the caller to free it
   * in case we encounter the error in the middle of the loop.
   */
  for (n_to_send = at_most_a_frame (burst), burst -= n_to_send; n_to_send > 0;
       n_to_send = at_most_a_frame (burst), burst -= n_to_send)
    {
      f = vlib_get_frame_to_node (vm, lookup_node_index);
      /* f can not be NULL here - frame allocation failure causes panic */

      u32 *to_next = vlib_frame_vector_args (f);
      f->n_vectors = n_to_send;

      while (n_to_send > 1)
	{
	  vlib_buffer_t *b0copy = vlib_buffer_copy (vm, b0);
	  if (PREDICT_FALSE (b0copy == NULL))
	    goto ship_and_ret;
	  *to_next++ = vlib_get_buffer_index (vm, b0copy);
	  n_to_send--;
	  n_sent++;
	}

      /* n_to_send is guaranteed to equal 1 here */
      if (burst > 0)
	{
	  /* not the last burst, so still make a copy for the last buffer */
	  vlib_buffer_t *b0copy = vlib_buffer_copy (vm, b0);
	  if (PREDICT_FALSE (b0copy == NULL))
	    goto ship_and_ret;
	  n_to_send--;
	  *to_next++ = vlib_get_buffer_index (vm, b0copy);
	}
      else
	{
	  /* put the original buffer as the last one of an error-free run */
	  *to_next++ = vlib_get_buffer_index (vm, b0);
	}
      vlib_put_frame_to_node (vm, lookup_node_index, f);
      n_sent += f->n_vectors;
    }
  return n_sent;
  /*
   * We reach here in case we already enqueued one or more buffers
   * and maybe one or more frames but could not make more copies.
   * There is an outstanding frame - so ship it and return.
   * Caller will have to free the b0 in this case, since
   * we did not enqueue it here yet.
   */
ship_and_ret:
  ASSERT (n_to_send <= f->n_vectors);
  f->n_vectors -= n_to_send;
  n_sent += f->n_vectors;
  vlib_put_frame_to_node (vm, lookup_node_index, f);
  return n_sent;
}


/*
 * An address-family agnostic ping send function.
 */

#define ERROR_OUT(e) do { err = e; goto done; } while (0)

static send_ip46_ping_result_t
send_ip46_ping (vlib_main_t * vm,
		u32 table_id,
		ip46_address_t * pa46,
		u32 sw_if_index,
		u16 seq_host, u16 id_host, u16 data_len, u32 burst,
		u8 verbose, int is_ip6)
{
  int err = SEND_PING_OK;
  u32 bi0 = 0;
  int n_buf0 = 0;
  vlib_buffer_t *b0;

  n_buf0 = vlib_buffer_alloc (vm, &bi0, 1);
  if (n_buf0 < 1)
    ERROR_OUT (SEND_PING_ALLOC_FAIL);

  b0 = vlib_get_buffer (vm, bi0);

  /*
   * if the user did not provide a source interface,
   * perform a resolution and use an interface
   * via which it succeeds.
   */
  u32 fib_index;
  if (~0 == sw_if_index)
    {
      fib_index = ip46_fib_index_from_table_id (table_id, is_ip6);
      sw_if_index = ip46_get_resolving_interface (fib_index, pa46, is_ip6);
    }
  else
    fib_index =
      ip46_fib_table_get_index_for_sw_if_index (sw_if_index, is_ip6, pa46);

  if (~0 == fib_index)
    ERROR_OUT (SEND_PING_NO_TABLE);
  if (~0 == sw_if_index)
    ERROR_OUT (SEND_PING_NO_INTERFACE);

  vnet_buffer (b0)->sw_if_index[VLIB_RX] = sw_if_index;
  vnet_buffer (b0)->sw_if_index[VLIB_TX] = fib_index;

  int l4_header_offset = ip46_fill_l3_header (pa46, b0, is_ip6);

  /* set the src address in the buffer */
  if (!ip46_set_src_address (sw_if_index, b0, is_ip6))
    ERROR_OUT (SEND_PING_NO_SRC_ADDRESS);
  if (verbose)
    ip46_print_buffer_src_address (vm, b0, is_ip6);

  data_len =
    ip46_fill_icmp_request_at (vm, l4_header_offset, seq_host, id_host,
			       data_len, b0, is_ip6);

  ip46_fix_len_and_csum (vm, l4_header_offset, data_len, b0, is_ip6);

  u32 node_index = ip6_lookup_node.index;
  if (is_ip6)
    {
      if (pa46->ip6.as_u32[0] == clib_host_to_net_u32 (0xff020000))
	{
	  node_index = ip6_rewrite_mcast_node.index;
	  vnet_buffer (b0)->sw_if_index[VLIB_RX] = sw_if_index;
	  vnet_buffer (b0)->sw_if_index[VLIB_TX] = sw_if_index;
	  vnet_buffer (b0)->ip.adj_index[VLIB_TX] =
	    ip6_link_get_mcast_adj (sw_if_index);
	}
    }
  else
    {
      node_index = ip4_lookup_node.index;
    }
  int n_sent = ip46_enqueue_packet (vm, b0, burst, node_index);
  if (n_sent < burst)
    err = SEND_PING_NO_BUFFERS;

done:
  if (err != SEND_PING_OK)
    {
      if (n_buf0 > 0)
	vlib_buffer_free (vm, &bi0, 1);
    }
  return err;
}

static send_ip46_ping_result_t
send_ip6_ping (vlib_main_t * vm,
	       u32 table_id, ip6_address_t * pa6,
	       u32 sw_if_index, u16 seq_host, u16 id_host, u16 data_len,
	       u32 burst, u8 verbose)
{
  ip46_address_t target;
  target.ip6 = *pa6;
  return send_ip46_ping (vm, table_id, &target, sw_if_index, seq_host,
			 id_host, data_len, burst, verbose, 1 /* is_ip6 */ );
}

static send_ip46_ping_result_t
send_ip4_ping (vlib_main_t * vm,
	       u32 table_id, ip4_address_t * pa4,
	       u32 sw_if_index, u16 seq_host, u16 id_host, u16 data_len,
	       u32 burst, u8 verbose)
{
  ip46_address_t target;
  ip46_address_set_ip4 (&target, pa4);
  return send_ip46_ping (vm, table_id, &target, sw_if_index, seq_host,
			 id_host, data_len, burst, verbose, 0 /* is_ip6 */ );
}

static void
print_ip46_icmp_reply (vlib_main_t * vm, u32 bi0, int is_ip6)
{
  vlib_buffer_t *b0 = vlib_get_buffer (vm, bi0);
  int l4_offset;
  void *paddr;
  void *format_addr_func;
  u16 payload_length;
  u8 ttl;
  if (is_ip6)
    {
      ip6_header_t *ip6 = vlib_buffer_get_current (b0);
      paddr = (void *) &ip6->src_address;
      format_addr_func = (void *) format_ip6_address;
      ttl = ip6->hop_limit;
      l4_offset = sizeof (ip6_header_t);	// FIXME - EH processing ?
      payload_length = clib_net_to_host_u16 (ip6->payload_length);
    }
  else
    {
      ip4_header_t *ip4 = vlib_buffer_get_current (b0);
      paddr = (void *) &ip4->src_address;
      format_addr_func = (void *) format_ip4_address;
      ttl = ip4->ttl;
      l4_offset = ip4_header_bytes (ip4);
      payload_length =
	clib_net_to_host_u16 (ip4->length) + ip4_header_bytes (ip4);
    }
  icmp46_header_t *icmp = vlib_buffer_get_current (b0) + l4_offset;
  icmp46_echo_request_t *icmp_echo = (icmp46_echo_request_t *) (icmp + 1);
  u64 *dataplane_ts = (u64 *) & vnet_buffer (b0)->unused[0];

  f64 clocks_per_second = ((f64) vm->clib_time.clocks_per_second);
  f64 rtt =
    ((f64) (*dataplane_ts - icmp_echo->time_sent)) / clocks_per_second;

  vlib_cli_output (vm,
		   "%d bytes from %U: icmp_seq=%d ttl=%d time=%.4f ms",
		   payload_length,
		   format_addr_func,
		   paddr,
		   clib_host_to_net_u16 (icmp_echo->seq), ttl, rtt * 1000.0);
}

/*
 * Perform the ping run with the given parameters in the current CLI process.
 * Depending on whether pa4 or pa6 is set, runs IPv4 or IPv6 ping.
 * The amusing side effect is of course if both are set, then both pings are sent.
 * This behavior can be used to ping a dualstack host over IPv4 and IPv6 at once.
 */

static void
run_ping_ip46_address (vlib_main_t * vm, u32 table_id, ip4_address_t * pa4,
		       ip6_address_t * pa6, u32 sw_if_index,
		       f64 ping_interval, u32 ping_repeat, u32 data_len,
		       u32 ping_burst, u32 verbose)
{
  int i;
  uword curr_proc = vlib_current_process (vm);
  u32 n_replies = 0;
  u32 n_requests = 0;
  u16 icmp_id;

  static u32 rand_seed = 0;

  if (PREDICT_FALSE (!rand_seed))
    rand_seed = random_default_seed ();

  icmp_id = random_u32 (&rand_seed) & 0xffff;

  while (~0 != get_cli_process_id_by_icmp_id_mt (vm, icmp_id))
    {
      vlib_cli_output (vm, "ICMP ID collision at %d, incrementing", icmp_id);
      icmp_id++;
    }

  set_cli_process_id_by_icmp_id_mt (vm, icmp_id, curr_proc);

  for (i = 1; i <= ping_repeat; i++)
    {
      send_ip46_ping_result_t res = SEND_PING_OK;
      f64 sleep_interval;
      f64 time_ping_sent = vlib_time_now (vm);
      if (pa6)
	{
	  res = send_ip6_ping (vm, table_id,
			       pa6, sw_if_index, i, icmp_id,
			       data_len, ping_burst, verbose);
	  if (SEND_PING_OK == res)
	    n_requests += ping_burst;
	  else
	    vlib_cli_output (vm, "Failed: %U", format_ip46_ping_result, res);
	}
      if (pa4)
	{
	  res = send_ip4_ping (vm, table_id, pa4,
			       sw_if_index, i, icmp_id, data_len,
			       ping_burst, verbose);
	  if (SEND_PING_OK == res)
	    n_requests += ping_burst;
	  else
	    vlib_cli_output (vm, "Failed: %U", format_ip46_ping_result, res);
	}

      /* Collect and print the responses until it is time to send a next ping */

      while ((i <= ping_repeat)
	     &&
	     ((sleep_interval =
	       time_ping_sent + ping_interval - vlib_time_now (vm)) > 0.0))
	{
	  uword event_type, *event_data = 0;
	  vlib_process_wait_for_event_or_clock (vm, sleep_interval);
	  event_type = vlib_process_get_events (vm, &event_data);
	  switch (event_type)
	    {
	    case ~0:		/* no events => timeout */
	      break;
	    case PING_RESPONSE_IP6:
	      /* fall-through */
	    case PING_RESPONSE_IP4:
	      {
		int ii;
		int is_ip6 = (event_type == PING_RESPONSE_IP6);
		for (ii = 0; ii < vec_len (event_data); ii++)
		  {
		    u32 bi0 = event_data[ii];
		    print_ip46_icmp_reply (vm, bi0, is_ip6);
		    n_replies++;
		    if (0 != bi0)
		      vlib_buffer_free (vm, &bi0, 1);
		  }
	      }
	      break;
	    case UNIX_CLI_PROCESS_EVENT_READ_READY:
	    case UNIX_CLI_PROCESS_EVENT_QUIT:
	      /* someone pressed a key, abort */
	      vlib_cli_output (vm, "Aborted due to a keypress.");
	      goto double_break;
	    }
	  vec_free (event_data);
	}
    }
double_break:
  vlib_cli_output (vm, "\n");
  {
    float loss =
      (0 ==
       n_requests) ? 0 : 100.0 * ((float) n_requests -
				  (float) n_replies) / (float) n_requests;
    vlib_cli_output (vm,
		     "Statistics: %u sent, %u received, %f%% packet loss\n",
		     n_requests, n_replies, loss);
    clear_cli_process_id_by_icmp_id_mt (vm, icmp_id);
  }
}



static clib_error_t *
ping_ip_address (vlib_main_t * vm,
		 unformat_input_t * input, vlib_cli_command_t * cmd)
{
  ip4_address_t a4;
  ip6_address_t a6;
  clib_error_t *error = 0;
  u32 ping_repeat = 5;
  u32 ping_burst = 1;
  u8 ping_ip4, ping_ip6;
  vnet_main_t *vnm = vnet_get_main ();
  u32 data_len = PING_DEFAULT_DATA_LEN;
  u32 verbose = 0;
  f64 ping_interval = PING_DEFAULT_INTERVAL;
  u32 sw_if_index, table_id;

  table_id = 0;
  ping_ip4 = ping_ip6 = 0;
  sw_if_index = ~0;

  if (unformat (input, "%U", unformat_ip4_address, &a4))
    {
      ping_ip4 = 1;
    }
  else if (unformat (input, "%U", unformat_ip6_address, &a6))
    {
      ping_ip6 = 1;
    }
  else if (unformat (input, "ipv4"))
    {
      if (unformat (input, "%U", unformat_ip4_address, &a4))
	{
	  ping_ip4 = 1;
	}
      else
	{
	  error =
	    clib_error_return (0,
			       "expecting IPv4 address but got `%U'",
			       format_unformat_error, input);
	}
    }
  else if (unformat (input, "ipv6"))
    {
      if (unformat (input, "%U", unformat_ip6_address, &a6))
	{
	  ping_ip6 = 1;
	}
      else
	{
	  error =
	    clib_error_return (0,
			       "expecting IPv6 address but got `%U'",
			       format_unformat_error, input);
	}
    }
  else
    {
      error =
	clib_error_return (0,
			   "expecting IP4/IP6 address `%U'. Usage: ping <addr> [source <intf>] [size <datasz>] [repeat <count>] [verbose]",
			   format_unformat_error, input);
      goto done;
    }

  /* allow for the second AF in the same ping */
  if (!ping_ip4 && (unformat (input, "ipv4")))
    {
      if (unformat (input, "%U", unformat_ip4_address, &a4))
	{
	  ping_ip4 = 1;
	}
    }
  else if (!ping_ip6 && (unformat (input, "ipv6")))
    {
      if (unformat (input, "%U", unformat_ip6_address, &a6))
	{
	  ping_ip6 = 1;
	}
    }

  /* parse the rest of the parameters  in a cycle */
  while (!unformat_eof (input, NULL))
    {
      if (unformat (input, "source"))
	{
	  if (!unformat_user
	      (input, unformat_vnet_sw_interface, vnm, &sw_if_index))
	    {
	      error =
		clib_error_return (0,
				   "unknown interface `%U'",
				   format_unformat_error, input);
	      goto done;
	    }
	}
      else if (unformat (input, "size"))
	{
	  if (!unformat (input, "%u", &data_len))
	    {
	      error =
		clib_error_return (0,
				   "expecting size but got `%U'",
				   format_unformat_error, input);
	      goto done;
	    }
	  if (data_len > PING_MAXIMUM_DATA_SIZE)
	    {
	      error =
		clib_error_return (0,
				   "%d is bigger than maximum allowed payload size %d",
				   data_len, PING_MAXIMUM_DATA_SIZE);
	      goto done;
	    }
	}
      else if (unformat (input, "table-id"))
	{
	  if (!unformat (input, "%u", &table_id))
	    {
	      error =
		clib_error_return (0,
				   "expecting table-id but got `%U'",
				   format_unformat_error, input);
	      goto done;
	    }
	}
      else if (unformat (input, "interval"))
	{
	  if (!unformat (input, "%f", &ping_interval))
	    {
	      error =
		clib_error_return (0,
				   "expecting interval (floating point number) got `%U'",
				   format_unformat_error, input);
	      goto done;
	    }
	}
      else if (unformat (input, "repeat"))
	{
	  if (!unformat (input, "%u", &ping_repeat))
	    {
	      error =
		clib_error_return (0,
				   "expecting repeat count but got `%U'",
				   format_unformat_error, input);
	      goto done;
	    }
	}
      else if (unformat (input, "burst"))
	{
	  if (!unformat (input, "%u", &ping_burst))
	    {
	      error =
		clib_error_return (0,
				   "expecting burst count but got `%U'",
				   format_unformat_error, input);
	      goto done;
	    }
	}
      else if (unformat (input, "verbose"))
	{
	  verbose = 1;
	}
      else
	{
	  error = clib_error_return (0, "unknown input `%U'",
				     format_unformat_error, input);
	  goto done;
	}
    }

/*
 * Operationally, one won't (and shouldn't) need to send more than a frame worth of pings.
 * But it may be handy during the debugging.
 */

#ifdef CLIB_DEBUG
#define MAX_PING_BURST (10*VLIB_FRAME_SIZE)
#else
#define MAX_PING_BURST (VLIB_FRAME_SIZE)
#endif

  if (ping_burst < 1 || ping_burst > MAX_PING_BURST)
    return clib_error_return (0, "burst size must be between 1 and %u",
			      MAX_PING_BURST);

  run_ping_ip46_address (vm, table_id, ping_ip4 ? &a4 : NULL,
			 ping_ip6 ? &a6 : NULL, sw_if_index, ping_interval,
			 ping_repeat, data_len, ping_burst, verbose);
done:
  return error;
}

/*?
 * This command sends an ICMP ECHO_REQUEST to network hosts. The address
 * can be an IPv4 or IPv6 address (or both at the same time).
 *
 * @cliexpar
 * @parblock
 * Example of how ping an IPv4 address:
 * @cliexstart{ping 172.16.1.2 source GigabitEthernet2/0/0 repeat 2}
 * 64 bytes from 172.16.1.2: icmp_seq=1 ttl=64 time=.1090 ms
 * 64 bytes from 172.16.1.2: icmp_seq=2 ttl=64 time=.0914 ms
 *
 * Statistics: 2 sent, 2 received, 0% packet loss
 * @cliexend
 *
 * Example of how ping both an IPv4 address and IPv6 address at the same time:
 * @cliexstart{ping 172.16.1.2 ipv6 fe80::24a5:f6ff:fe9c:3a36 source GigabitEthernet2/0/0 repeat 2 verbose}
 * Adjacency index: 10, sw_if_index: 1
 * Adj: ip6-discover-neighbor
 * Adj Interface: 0
 * Forced set interface: 1
 * Adjacency index: 0, sw_if_index: 4294967295
 * Adj: ip4-miss
 * Adj Interface: 0
 * Forced set interface: 1
 * Source address: 172.16.1.1
 * 64 bytes from 172.16.1.2: icmp_seq=1 ttl=64 time=.1899 ms
 * Adjacency index: 10, sw_if_index: 1
 * Adj: ip6-discover-neighbor
 * Adj Interface: 0
 * Forced set interface: 1
 * Adjacency index: 0, sw_if_index: 4294967295
 * Adj: ip4-miss
 * Adj Interface: 0
 * Forced set interface: 1
 * Source address: 172.16.1.1
 * 64 bytes from 172.16.1.2: icmp_seq=2 ttl=64 time=.0910 ms
 *
 * Statistics: 4 sent, 2 received, 50% packet loss
 * @cliexend
 * @endparblock
?*/
/* *INDENT-OFF* */
VLIB_CLI_COMMAND (ping_command, static) =
{
  .path = "ping",
  .function = ping_ip_address,
  .short_help = "ping {<ip-addr> | ipv4 <ip4-addr> | ipv6 <ip6-addr>}"
  " [ipv4 <ip4-addr> | ipv6 <ip6-addr>] [source <interface>]"
  " [size <pktsize:60>] [interval <sec:1>] [repeat <cnt:5>] [table-id <id:0>]"
  " [burst <count:1>] [verbose]",
  .is_mp_safe = 1,
};
/* *INDENT-ON* */

static clib_error_t *
ping_cli_init (vlib_main_t * vm)
{
  vlib_thread_main_t *tm = vlib_get_thread_main ();
  ping_main_t *pm = &ping_main;

  pm->ip6_main = &ip6_main;
  pm->ip4_main = &ip4_main;
  icmp6_register_type (vm, ICMP6_echo_reply, ip6_icmp_echo_reply_node.index);
  ip4_icmp_register_type (vm, ICMP4_echo_reply,
			  ip4_icmp_echo_reply_node.index);
  if (tm->n_vlib_mains > 1)
    clib_spinlock_init (&pm->ping_run_check_lock);

  ip4_icmp_register_type (vm, ICMP4_echo_request,
			  ip4_icmp_echo_request_node.index);
  icmp6_register_type (vm, ICMP6_echo_request,
		       ip6_icmp_echo_request_node.index);

  return 0;
}

VLIB_INIT_FUNCTION (ping_cli_init);

/* *INDENT-OFF* */
VLIB_PLUGIN_REGISTER () = {
    .version = VPP_BUILD_VER,
    .description = "Ping (ping)",
};
/* *INDENT-ON* */

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