/* * Copyright (c) 2017 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 #include static clib_error_t * lisp_show_adjacencies_command_fn (vlib_main_t * vm, unformat_input_t * input, vlib_cli_command_t * cmd) { lisp_adjacency_t *adjs, *adj; vlib_cli_output (vm, "%s %40s\n", "leid", "reid"); unformat_input_t _line_input, *line_input = &_line_input; u32 vni = ~0; /* Get a line of input. */ if (!unformat_user (input, unformat_line_input, line_input)) return 0; while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT) { if (unformat (line_input, "vni %d", &vni)) ; else { vlib_cli_output (vm, "parse error: '%U'", format_unformat_error, line_input); unformat_free (line_input); return 0; } } unformat_free (line_input); if (~0 == vni) { vlib_cli_output (vm, "error: no vni specified!"); return 0; } adjs = vnet_lisp_adjacencies_get_by_vni (vni); vec_foreach (adj, adjs) { vlib_cli_output (vm, "%U %40U\n", format_gid_address, &adj->leid, format_gid_address, &adj->reid); } vec_free (adjs); return 0; } /* *INDENT-OFF* */ VLIB_CLI_COMMAND (one_show_adjacencies_command) = { .path = "show one adjacencies", .short_help = "show one adjacencies", .function = lisp_show_adjacencies_command_fn, }; /* *INDENT-ON* */ static clib_error_t * lisp_add_del_map_server_command_fn (vlib_main_t * vm, unformat_input_t * input, vlib_cli_command_t * cmd) { int rv = 0; u8 is_add = 1, ip_set = 0; ip_address_t ip; unformat_input_t _line_input, *line_input = &_line_input; /* Get a line of input. */ if (!unformat_user (input, unformat_line_input, line_input)) return 0; while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT) { if (unformat (line_input, "add")) is_add = 1; else if (unformat (line_input, "del")) is_add = 0; else if (unformat (line_input, "%U", unformat_ip_address, &ip)) ip_set = 1; else { vlib_cli_output (vm, "parse error: '%U'", format_unformat_error, line_input); unformat_free (line_input); return 0; } } unformat_free (line_input); if (!ip_set) { vlib_cli_output (vm, "map-server ip address not set!"); return 0; } rv = vnet_lisp_add_del_map_server (&ip, is_add); if (!rv) vlib_cli_output (vm, "failed to %s map-server!", is_add ? "add" : "delete"); return 0; } /* *INDENT-OFF* */ VLIB_CLI_COMMAND (one_add_del_map_server_command) = { .path = "one map-server", .short_help = "one map-server add|del ", .function = lisp_add_del_map_server_command_fn, }; /* *INDENT-ON* */ static clib_error_t * lisp_add_del_local_eid_command_fn (vlib_main_t * vm, unformat_input_t * input, vlib_cli_command_t * cmd) { lisp_cp_main_t *lcm = vnet_lisp_cp_get_main (); unformat_input_t _line_input, *line_input = &_line_input; u8 is_add = 1; gid_address_t eid; gid_address_t *eids = 0; clib_error_t *error = 0; u8 *locator_set_name = 0; u32 locator_set_index = 0, map_index = 0; uword *p; vnet_lisp_add_del_mapping_args_t _a, *a = &_a; int rv = 0; u32 vni = 0; u8 *key = 0; u32 key_id = 0; memset (&eid, 0, sizeof (eid)); memset (a, 0, sizeof (*a)); /* Get a line of input. */ if (!unformat_user (input, unformat_line_input, line_input)) return 0; while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT) { if (unformat (line_input, "add")) is_add = 1; else if (unformat (line_input, "del")) is_add = 0; else if (unformat (line_input, "eid %U", unformat_gid_address, &eid)) ; else if (unformat (line_input, "vni %d", &vni)) gid_address_vni (&eid) = vni; else if (unformat (line_input, "secret-key %_%v%_", &key)) ; else if (unformat (line_input, "key-id %U", unformat_hmac_key_id, &key_id)) ; else if (unformat (line_input, "locator-set %_%v%_", &locator_set_name)) { p = hash_get_mem (lcm->locator_set_index_by_name, locator_set_name); if (!p) { error = clib_error_return (0, "locator-set %s doesn't exist", locator_set_name); goto done; } locator_set_index = p[0]; } else { error = unformat_parse_error (line_input); goto done; } } /* XXX treat batch configuration */ if (GID_ADDR_SRC_DST == gid_address_type (&eid)) { error = clib_error_return (0, "src/dst is not supported for local EIDs!"); goto done; } if (key && (0 == key_id)) { vlib_cli_output (vm, "invalid key_id!"); goto done; } gid_address_copy (&a->eid, &eid); a->is_add = is_add; a->locator_set_index = locator_set_index; a->local = 1; a->key = key; a->key_id = key_id; rv = vnet_lisp_add_del_local_mapping (a, &map_index); if (0 != rv) { error = clib_error_return (0, "failed to %s local mapping!", is_add ? "add" : "delete"); } done: vec_free (eids); if (locator_set_name) vec_free (locator_set_name); gid_address_free (&a->eid); vec_free (a->key); unformat_free (line_input); return error; } /* *INDENT-OFF* */ VLIB_CLI_COMMAND (one_add_del_local_eid_command) = { .path = "one eid-table", .short_help = "one eid-table add/del [vni ] eid " "locator-set [key key-id sha1|sha256 ]", .function = lisp_add_del_local_eid_command_fn, }; /* *INDENT-ON* */ static clib_error_t * lisp_eid_table_map_command_fn (vlib_main_t * vm, unformat_input_t * input, vlib_cli_command_t * cmd) { u8 is_add = 1, is_l2 = 0; u32 vni = 0, dp_id = 0; unformat_input_t _line_input, *line_input = &_line_input; clib_error_t *error = NULL; /* Get a line of input. */ if (!unformat_user (input, unformat_line_input, line_input)) return 0; while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT) { if (unformat (line_input, "del")) is_add = 0; else if (unformat (line_input, "vni %d", &vni)) ; else if (unformat (line_input, "vrf %d", &dp_id)) ; else if (unformat (line_input, "bd %d", &dp_id)) is_l2 = 1; else { error = unformat_parse_error (line_input); goto done; } } vnet_lisp_eid_table_map (vni, dp_id, is_l2, is_add); done: unformat_free (line_input); return error; } /* *INDENT-OFF* */ VLIB_CLI_COMMAND (one_eid_table_map_command) = { .path = "one eid-table map", .short_help = "one eid-table map [del] vni vrf | bd ", .function = lisp_eid_table_map_command_fn, }; /* *INDENT-ON* */ /** * Handler for add/del remote mapping CLI. * * @param vm vlib context * @param input input from user * @param cmd cmd * @return pointer to clib error structure */ static clib_error_t * lisp_add_del_remote_mapping_command_fn (vlib_main_t * vm, unformat_input_t * input, vlib_cli_command_t * cmd) { clib_error_t *error = 0; unformat_input_t _line_input, *line_input = &_line_input; u8 is_add = 1, del_all = 0; locator_t rloc, *rlocs = 0, *curr_rloc = 0; gid_address_t eid; u8 eid_set = 0; u32 vni, action = ~0, p, w; int rv; /* Get a line of input. */ if (!unformat_user (input, unformat_line_input, line_input)) return 0; memset (&eid, 0, sizeof (eid)); memset (&rloc, 0, sizeof (rloc)); while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT) { if (unformat (line_input, "del-all")) del_all = 1; else if (unformat (line_input, "del")) is_add = 0; else if (unformat (line_input, "add")) ; else if (unformat (line_input, "eid %U", unformat_gid_address, &eid)) eid_set = 1; else if (unformat (line_input, "vni %u", &vni)) { gid_address_vni (&eid) = vni; } else if (unformat (line_input, "p %d w %d", &p, &w)) { if (!curr_rloc) { clib_warning ("No RLOC configured for setting priority/weight!"); goto done; } curr_rloc->priority = p; curr_rloc->weight = w; } else if (unformat (line_input, "rloc %U", unformat_ip_address, &gid_address_ip (&rloc.address))) { /* since rloc is stored in ip prefix we need to set prefix length */ ip_prefix_t *pref = &gid_address_ippref (&rloc.address); u8 version = gid_address_ip_version (&rloc.address); ip_prefix_len (pref) = ip_address_max_len (version); vec_add1 (rlocs, rloc); curr_rloc = &rlocs[vec_len (rlocs) - 1]; } else if (unformat (line_input, "action %U", unformat_negative_mapping_action, &action)) ; else { clib_warning ("parse error"); goto done; } } if (!del_all && !eid_set) { clib_warning ("missing eid!"); goto done; } if (!del_all) { if (is_add && (~0 == action) && 0 == vec_len (rlocs)) { clib_warning ("no action set for negative map-reply!"); goto done; } } else { vnet_lisp_clear_all_remote_adjacencies (); goto done; } /* if it's a delete, clean forwarding */ if (!is_add) { vnet_lisp_add_del_adjacency_args_t _a, *a = &_a; memset (a, 0, sizeof (a[0])); gid_address_copy (&a->reid, &eid); if (vnet_lisp_add_del_adjacency (a)) { clib_warning ("failed to delete adjacency!"); goto done; } } /* add as static remote mapping, i.e., not authoritative and infinite * ttl */ rv = vnet_lisp_add_del_mapping (&eid, rlocs, action, 0, ~0, is_add, 1 /* is_static */ , 0); if (rv) clib_warning ("failed to %s remote mapping!", is_add ? "add" : "delete"); done: vec_free (rlocs); unformat_free (line_input); return error; } /* *INDENT-OFF* */ VLIB_CLI_COMMAND (one_add_del_remote_mapping_command) = { .path = "one remote-mapping", .short_help = "one remote-mapping add|del [del-all] vni " "eid [action ] rloc p w " "[rloc ... ]", .function = lisp_add_del_remote_mapping_command_fn, }; /* *INDENT-ON* */ /** * Handler for add/del adjacency CLI. */ static clib_error_t * lisp_add_del_adjacency_command_fn (vlib_main_t * vm, unformat_input_t * input, vlib_cli_command_t * cmd) { clib_error_t *error = 0; unformat_input_t _line_input, *line_input = &_line_input; vnet_lisp_add_del_adjacency_args_t _a, *a = &_a; u8 is_add = 1; ip_prefix_t *reid_ippref, *leid_ippref; gid_address_t leid, reid; u8 *dmac = gid_address_mac (&reid); u8 *smac = gid_address_mac (&leid); u8 reid_set = 0, leid_set = 0; u32 vni; /* Get a line of input. */ if (!unformat_user (input, unformat_line_input, line_input)) return 0; memset (&reid, 0, sizeof (reid)); memset (&leid, 0, sizeof (leid)); leid_ippref = &gid_address_ippref (&leid); reid_ippref = &gid_address_ippref (&reid); while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT) { if (unformat (line_input, "del")) is_add = 0; else if (unformat (line_input, "add")) ; else if (unformat (line_input, "reid %U", unformat_ip_prefix, reid_ippref)) { gid_address_type (&reid) = GID_ADDR_IP_PREFIX; reid_set = 1; } else if (unformat (line_input, "reid %U", unformat_mac_address, dmac)) { gid_address_type (&reid) = GID_ADDR_MAC; reid_set = 1; } else if (unformat (line_input, "vni %u", &vni)) { gid_address_vni (&leid) = vni; gid_address_vni (&reid) = vni; } else if (unformat (line_input, "leid %U", unformat_ip_prefix, leid_ippref)) { gid_address_type (&leid) = GID_ADDR_IP_PREFIX; leid_set = 1; } else if (unformat (line_input, "leid %U", unformat_mac_address, smac)) { gid_address_type (&leid) = GID_ADDR_MAC; leid_set = 1; } else { clib_warning ("parse error"); goto done; } } if (!reid_set || !leid_set) { clib_warning ("missing remote or local eid!"); goto done; } if ((gid_address_type (&leid) != gid_address_type (&reid)) || (gid_address_type (&reid) == GID_ADDR_IP_PREFIX && ip_prefix_version (reid_ippref) != ip_prefix_version (leid_ippref))) { clib_warning ("remote and local EIDs are of different types!"); goto done; } memset (a, 0, sizeof (a[0])); gid_address_copy (&a->leid, &leid); gid_address_copy (&a->reid, &reid); a->is_add = is_add; if (vnet_lisp_add_del_adjacency (a)) clib_warning ("failed to %s adjacency!", is_add ? "add" : "delete"); done: unformat_free (line_input); return error; } /* *INDENT-OFF* */ VLIB_CLI_COMMAND (one_add_del_adjacency_command) = { .path = "one adjacency", .short_help = "one adjacency add|del vni reid " "leid ", .function = lisp_add_del_adjacency_command_fn, }; /* *INDENT-ON* */ static clib_error_t * lisp_map_request_mode_command_fn (vlib_main_t * vm, unformat_input_t * input, vlib_cli_command_t * cmd) { unformat_input_t _i, *i = &_i; map_request_mode_t mr_mode = _MR_MODE_MAX; /* Get a line of input. */ if (!unformat_user (input, unformat_line_input, i)) return 0; while (unformat_check_input (i) != UNFORMAT_END_OF_INPUT) { if (unformat (i, "dst-only")) mr_mode = MR_MODE_DST_ONLY; else if (unformat (i, "src-dst")) mr_mode = MR_MODE_SRC_DST; else { clib_warning ("parse error '%U'", format_unformat_error, i); goto done; } } if (_MR_MODE_MAX == mr_mode) { clib_warning ("No map request mode entered!"); goto done; } vnet_lisp_set_map_request_mode (mr_mode); done: unformat_free (i); return 0; } /* *INDENT-OFF* */ VLIB_CLI_COMMAND (one_map_request_mode_command) = { .path = "one map-request mode", .short_help = "one map-request mode dst-only|src-dst", .function = lisp_map_request_mode_command_fn, }; /* *INDENT-ON* */ static u8 * format_lisp_map_request_mode (u8 * s, va_list * args) { u32 mode = va_arg (*args, u32); switch (mode) { case 0: return format (0, "dst-only"); case 1: return format (0, "src-dst"); } return 0; } static clib_error_t * lisp_show_map_request_mode_command_fn (vlib_main_t * vm, unformat_input_t * input, vlib_cli_command_t * cmd) { vlib_cli_output (vm, "map-request mode: %U", format_lisp_map_request_mode, vnet_lisp_get_map_request_mode ()); return 0; } /* *INDENT-OFF* */ VLIB_CLI_COMMAND (one_show_map_request_mode_command) = { .path = "show one map-request mode", .short_help = "show one map-request mode", .function = lisp_show_map_request_mode_command_fn, }; /* *INDENT-ON* */ static clib_error_t * lisp_show_map_resolvers_command_fn (vlib_main_t * vm, unformat_input_t * input, vlib_cli_command_t * cmd) { lisp_msmr_t *mr; lisp_cp_main_t *lcm = vnet_lisp_cp_get_main (); vec_foreach (mr, lcm->map_resolvers) { vlib_cli_output (vm, "%U", format_ip_address, &mr->address); } return 0; } /* *INDENT-OFF* */ VLIB_CLI_COMMAND (one_show_map_resolvers_command) = { .path = "show one map-resolvers", .short_help = "show one map-resolvers", .function = lisp_show_map_resolvers_command_fn, }; /* *INDENT-ON* */ static clib_error_t * lisp_pitr_set_locator_set_command_fn (vlib_main_t * vm, unformat_input_t * input, vlib_cli_command_t * cmd) { u8 locator_name_set = 0; u8 *locator_set_name = 0; u8 is_add = 1; unformat_input_t _line_input, *line_input = &_line_input; clib_error_t *error = 0; int rv = 0; /* Get a line of input. */ if (!unformat_user (input, unformat_line_input, line_input)) return 0; while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT) { if (unformat (line_input, "ls %_%v%_", &locator_set_name)) locator_name_set = 1; else if (unformat (line_input, "disable")) is_add = 0; else { error = clib_error_return (0, "parse error"); goto done; } } if (!locator_name_set) { clib_warning ("No locator set specified!"); goto done; } rv = vnet_lisp_pitr_set_locator_set (locator_set_name, is_add); if (0 != rv) { error = clib_error_return (0, "failed to %s pitr!", is_add ? "add" : "delete"); } done: if (locator_set_name) vec_free (locator_set_name); unformat_free (line_input); return error; } /* *INDENT-OFF* */ VLIB_CLI_COMMAND (one_pitr_set_locator_set_command) = { .path = "one pitr", .short_help = "one pitr [disable] ls ", .function = lisp_pitr_set_locator_set_command_fn, }; /* *INDENT-ON* */ static clib_error_t * lisp_show_pitr_command_fn (vlib_main_t * vm, unformat_input_t * input, vlib_cli_command_t * cmd) { lisp_cp_main_t *lcm = vnet_lisp_cp_get_main (); mapping_t *m; locator_set_t *ls; u8 *tmp_str = 0; vlib_cli_output (vm, "%=20s%=16s", "pitr", lcm->lisp_pitr ? "locator-set" : ""); if (!lcm->lisp_pitr) { vlib_cli_output (vm, "%=20s", "disable"); return 0; } if (~0 == lcm->pitr_map_index) { tmp_str = format (0, "N/A"); } else { m = pool_elt_at_index (lcm->mapping_pool, lcm->pitr_map_index); if (~0 != m->locator_set_index) { ls = pool_elt_at_index (lcm->locator_set_pool, m->locator_set_index); tmp_str = format (0, "%s", ls->name); } else { tmp_str = format (0, "N/A"); } } vec_add1 (tmp_str, 0); vlib_cli_output (vm, "%=20s%=16s", "enable", tmp_str); vec_free (tmp_str); return 0; } /* *INDENT-OFF* */ VLIB_CLI_COMMAND (one_show_pitr_command) = { .path = "show one pitr", .short_help = "Show pitr", .function = lisp_show_pitr_command_fn, }; /* *INDENT-ON* */ static u8 * format_eid_entry (u8 * s, va_list * args) { vnet_main_t *vnm = va_arg (*args, vnet_main_t *); lisp_cp_main_t *lcm = va_arg (*args, lisp_cp_main_t *); mapping_t *mapit = va_arg (*args, mapping_t *); locator_set_t *ls = va_arg (*args, locator_set_t *); gid_address_t *gid = &mapit->eid; u32 ttl = mapit->ttl; u8 aut = mapit->authoritative; u32 *loc_index; u8 first_line = 1; u8 *loc; u8 *type = ls->local ? format (0, "local(%s)", ls->name) : format (0, "remote"); if (vec_len (ls->locator_indices) == 0) { s = format (s, "%-35U%-30s%-20u%-u", format_gid_address, gid, type, ttl, aut); } else { vec_foreach (loc_index, ls->locator_indices) { locator_t *l = pool_elt_at_index (lcm->locator_pool, loc_index[0]); if (l->local) loc = format (0, "%U", format_vnet_sw_if_index_name, vnm, l->sw_if_index); else loc = format (0, "%U", format_ip_address, &gid_address_ip (&l->address)); if (first_line) { s = format (s, "%-35U%-20s%-30v%-20u%-u\n", format_gid_address, gid, type, loc, ttl, aut); first_line = 0; } else s = format (s, "%55s%v\n", "", loc); } } return s; } static clib_error_t * lisp_show_eid_table_command_fn (vlib_main_t * vm, unformat_input_t * input, vlib_cli_command_t * cmd) { lisp_cp_main_t *lcm = vnet_lisp_cp_get_main (); mapping_t *mapit; unformat_input_t _line_input, *line_input = &_line_input; u32 mi; gid_address_t eid; u8 print_all = 1; u8 filter = 0; clib_error_t *error = NULL; memset (&eid, 0, sizeof (eid)); /* Get a line of input. */ if (!unformat_user (input, unformat_line_input, line_input)) return 0; while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT) { if (unformat (line_input, "eid %U", unformat_gid_address, &eid)) print_all = 0; else if (unformat (line_input, "local")) filter = 1; else if (unformat (line_input, "remote")) filter = 2; else { error = clib_error_return (0, "parse error: '%U'", format_unformat_error, line_input); goto done; } } vlib_cli_output (vm, "%-35s%-20s%-30s%-20s%-s", "EID", "type", "locators", "ttl", "autoritative"); if (print_all) { /* *INDENT-OFF* */ pool_foreach (mapit, lcm->mapping_pool, ({ if (mapit->pitr_set) continue; locator_set_t * ls = pool_elt_at_index (lcm->locator_set_pool, mapit->locator_set_index); if (filter && !((1 == filter && ls->local) || (2 == filter && !ls->local))) { continue; } vlib_cli_output (vm, "%U", format_eid_entry, lcm->vnet_main, lcm, mapit, ls); })); /* *INDENT-ON* */ } else { mi = gid_dictionary_lookup (&lcm->mapping_index_by_gid, &eid);
/*
 * Copyright (c) 2015 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.
 */
/*
  Copyright (c) 2001, 2002, 2003 Eliot Dresselhaus

  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, including
  without limitation the rights to use, copy, modify, merge, publish,
  distribute, sublicense, and/or sell copies of the Software, and to
  permit persons to whom the Software is furnished to do so, subject to
  the following conditions:

  The above copyright notice and this permission notice shall be
  included in all copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

#include <vppinfra/format.h>
#include <ctype.h>

/* Format vectors. */
u8 *
format_vec32 (u8 * s, va_list * va)
{
  u32 *v = va_arg (*va, u32 *);
  char *fmt = va_arg (*va, char *);
  uword i;
  for (i = 0; i < vec_len (v); i++)
    {
      if (i > 0)
	s = format (s, ", ");
      s = format (s, fmt, v[i]);
    }
  return s;
}

u8 *
format_vec_uword (u8 * s, va_list * va)
{
  uword *v = va_arg (*va, uword *);
  char *fmt = va_arg (*va, char *);
  uword i;
  for (i = 0; i < vec_len (v); i++)
    {
      if (i > 0)
	s = format (s, ", ");
      s = format (s, fmt, v[i]);
    }
  return s;
}

/* Ascii buffer and length. */
u8 *
format_ascii_bytes (u8 * s, va_list * va)
{
  u8 *v = va_arg (*va, u8 *);
  uword n_bytes = va_arg (*va, uword);
  vec_add (s, v, n_bytes);
  return s;
}

/* Format hex dump. */
u8 *
format_hex_bytes (u8 * s, va_list * va)
{
  u8 *bytes = va_arg (*va, u8 *);
  int n_bytes = va_arg (*va, int);
  uword i;

  /* Print short or long form depending on byte count. */
  uword short_form = n_bytes <= 32;
  u32 indent = format_get_indent (s);

  if (n_bytes == 0)
    return s;

  for (i = 0; i < n_bytes; i++)
    {
      if (!short_form && (i % 32) == 0)
	s = format (s, "%08x: ", i);

      s = format (s, "%02x", bytes[i]);

      if (!short_form && ((i + 1) % 32) == 0 && (i + 1) < n_bytes)
	s = format (s, "\n%U", format_white_space, indent);
    }

  return s;
}

u8 *
format_hex_bytes_no_wrap (u8 * s, va_list * va)
{
  u8 *bytes = va_arg (*va, u8 *);
  int n_bytes