+static int
+gbp_endpoint_find_for_update (const ip46_address_t * ips,
+ const gbp_route_domain_t * grd,
+ const mac_address_t * mac,
+ const gbp_bridge_domain_t * gbd,
+ gbp_endpoint_t ** ge)
+{
+ gbp_endpoint_t *l2_ge, *l3_ge, *tmp;
+
+ l2_ge = l3_ge = NULL;
+
+ if (NULL != mac && !mac_address_is_zero (mac))
+ {
+ ASSERT (gbd);
+ l2_ge = gbp_endpoint_find_mac (mac->bytes, gbd->gb_bd_index);
+ }
+ if (NULL != ips && !ip46_address_is_zero (ips))
+ {
+ const ip46_address_t *ip;
+ fib_protocol_t fproto;
+
+ ASSERT (grd);
+ vec_foreach (ip, ips)
+ {
+ fproto = fib_proto_from_ip46 (ip46_address_get_type (ip));
+
+ tmp = gbp_endpoint_find_ip (ip, grd->grd_fib_index[fproto]);
+
+ if (NULL == tmp)
+ /* not found */
+ continue;
+ else if (NULL == l3_ge)
+ /* first match against an IP address */
+ l3_ge = tmp;
+ else if (tmp == l3_ge)
+ /* another match against IP address that is the same endpoint */
+ continue;
+ else
+ {
+ /*
+ * a match agains a different endpoint.
+ * this means the KEY of the EP is changing which is not allowed
+ */
+ return (-1);
+ }
+ }
+ }
+
+ if (NULL == l2_ge && NULL == l3_ge)
+ /* not found */
+ *ge = NULL;
+ else if (NULL == l2_ge)
+ /* found at L3 */
+ *ge = l3_ge;
+ else if (NULL == l3_ge)
+ /* found at L2 */
+ *ge = l2_ge;
+ else
+ {
+ /* found both L3 and L2 - they must be the same else the KEY
+ * is changing
+ */
+ if (l2_ge == l3_ge)
+ *ge = l2_ge;
+ else
+ return (-1);
+ }
+
+ return (0);
+}
+
+static gbp_endpoint_src_t
+gbp_endpoint_get_best_src (const gbp_endpoint_t * ge)
+{
+ if (0 == vec_len (ge->ge_locs))
+ return (GBP_ENDPOINT_SRC_MAX);
+
+ return (ge->ge_locs[0].gel_src);
+}
+
+static void
+gbp_endpoint_n_learned (int n)
+{
+ gbp_n_learnt_endpoints += n;
+
+ if (n > 0 && 1 == gbp_n_learnt_endpoints)
+ {
+ vlib_process_signal_event (vlib_get_main (),
+ gbp_scanner_node.index,
+ GBP_ENDPOINT_SCAN_START, 0);
+ }
+ if (n < 0 && 0 == gbp_n_learnt_endpoints)
+ {
+ vlib_process_signal_event (vlib_get_main (),
+ gbp_scanner_node.index,
+ GBP_ENDPOINT_SCAN_STOP, 0);
+ }
+}
+
+static void
+gbp_endpoint_loc_update (const gbp_endpoint_t * ge,
+ gbp_endpoint_loc_t * gel,
+ const gbp_bridge_domain_t * gb,
+ u32 sw_if_index,
+ index_t ggi,
+ gbp_endpoint_flags_t flags,
+ const ip46_address_t * tun_src,
+ const ip46_address_t * tun_dst)
+{
+ int was_learnt, is_learnt;
+
+ gel->gel_locks++;
+ was_learnt = ! !(gel->gel_flags & GBP_ENDPOINT_FLAG_REMOTE);
+ gel->gel_flags = flags;
+ is_learnt = ! !(gel->gel_flags & GBP_ENDPOINT_FLAG_REMOTE);
+
+ gbp_endpoint_n_learned (is_learnt - was_learnt);
+
+ /*
+ * update the EPG
+ */
+ gbp_endpoint_group_lock (ggi);
+ gbp_endpoint_group_unlock (gel->gel_epg);
+ gel->gel_epg = ggi;
+
+ if (gel->gel_flags & GBP_ENDPOINT_FLAG_REMOTE)
+ {
+ if (NULL != tun_src)
+ ip46_address_copy (&gel->tun.gel_src, tun_src);
+ if (NULL != tun_dst)
+ ip46_address_copy (&gel->tun.gel_dst, tun_dst);
+
+ if (ip46_address_is_multicast (&gel->tun.gel_src))
+ {
+ /*
+ * we learnt the EP from the multicast tunnel.
+ * Create a unicast TEP from the packet's source
+ * and the fixed address of the BD's parent tunnel
+ */
+ const gbp_vxlan_tunnel_t *gt;
+
+ gt = gbp_vxlan_tunnel_get (gb->gb_vni);
+
+ if (NULL != gt)
+ {
+ ip46_address_copy (&gel->tun.gel_src, >->gt_src);
+ sw_if_index = gt->gt_sw_if_index;
+ }
+ }
+
+ /*
+ * the input interface may be the parent GBP-vxlan interface,
+ * create a child vlxan-gbp tunnel and use that as the endpoint's
+ * interface.
+ */
+ gbp_itf_hdl_t old = gel->gel_itf;
+
+ switch (gbp_vxlan_tunnel_get_type (sw_if_index))
+ {
+ case GBP_VXLAN_TEMPLATE_TUNNEL:
+ gel->tun.gel_parent_sw_if_index = sw_if_index;
+ gel->gel_itf = gbp_vxlan_tunnel_clone_and_lock (sw_if_index,
+ &gel->tun.gel_src,
+ &gel->tun.gel_dst);
+ break;
+ case VXLAN_GBP_TUNNEL:
+ gel->tun.gel_parent_sw_if_index =
+ vxlan_gbp_tunnel_get_parent (sw_if_index);
+ gel->gel_itf = vxlan_gbp_tunnel_lock_itf (sw_if_index);
+ break;
+ }
+
+ gbp_itf_unlock (&old);
+ }
+ else
+ {
+ gel->gel_itf = gbp_itf_l2_add_and_lock (sw_if_index,
+ ge->ge_key.gek_gbd);
+ }
+}
+
+static void
+gbb_endpoint_fwd_reset (gbp_endpoint_t * ge)
+{
+ const gbp_route_domain_t *grd;
+ const gbp_bridge_domain_t *gbd;
+ gbp_endpoint_fwd_t *gef;
+ const fib_prefix_t *pfx;
+ index_t *ai;
+
+ gbd = gbp_bridge_domain_get (ge->ge_key.gek_gbd);
+ gef = &ge->ge_fwd;
+
+ vec_foreach (pfx, ge->ge_key.gek_ips)
+ {
+ u32 fib_index;
+
+ grd = gbp_route_domain_get (ge->ge_key.gek_grd);
+ fib_index = grd->grd_fib_index[pfx->fp_proto];
+
+ bd_add_del_ip_mac (gbd->gb_bd_index, fib_proto_to_ip46 (pfx->fp_proto),
+ &pfx->fp_addr, &ge->ge_key.gek_mac, 0);
+
+ /*
+ * remove a host route
+ */
+ if (gbp_endpoint_is_remote (ge))
+ {
+ fib_table_entry_special_remove (fib_index, pfx, gbp_fib_source_hi);
+ }
+
+ fib_table_entry_delete (fib_index, pfx, gbp_fib_source_low);
+ }
+ vec_foreach (ai, gef->gef_adjs)
+ {
+ adj_unlock (*ai);
+ }
+
+ if (gbp_itf_hdl_is_valid (gef->gef_itf))
+ {
+ l2fib_del_entry (ge->ge_key.gek_mac.bytes,
+ gbd->gb_bd_index,
+ gbp_itf_get_sw_if_index (gef->gef_itf));
+ }
+
+ gbp_itf_unlock (&gef->gef_itf);
+ vec_free (gef->gef_adjs);
+}
+
+static void
+gbb_endpoint_fwd_recalc (gbp_endpoint_t * ge)
+{
+ const gbp_bridge_domain_t *gbd;
+ const gbp_endpoint_group_t *gg;
+ const gbp_route_domain_t *grd;
+ gbp_endpoint_loc_t *gel;
+ gbp_endpoint_fwd_t *gef;
+ const fib_prefix_t *pfx;
+ index_t gei;
+
+ /*
+ * locations are sort in source priority order
+ */
+ gei = gbp_endpoint_index (ge);
+ gel = &ge->ge_locs[0];
+ gef = &ge->ge_fwd;
+ gbd = gbp_bridge_domain_get (ge->ge_key.gek_gbd);
+
+ gef->gef_flags = gel->gel_flags;
+
+ if (INDEX_INVALID != gel->gel_epg)
+ {
+ gg = gbp_endpoint_group_get (gel->gel_epg);
+ gef->gef_sclass = gg->gg_sclass;
+ }
+ else
+ {
+ gg = NULL;
+ }
+
+ gef->gef_itf = gbp_itf_clone_and_lock (gel->gel_itf);
+
+ if (!mac_address_is_zero (&ge->ge_key.gek_mac))
+ {
+ gbp_itf_l2_set_input_feature (gef->gef_itf, L2INPUT_FEAT_GBP_FWD);
+
+ if (gbp_endpoint_is_remote (ge) || gbp_endpoint_is_external (ge))
+ {
+ /*
+ * bridged packets to external endpoints should be classifed
+ * based on the EP's/BD's EPG
+ */
+ gbp_itf_l2_set_output_feature (gef->gef_itf,
+ L2OUTPUT_FEAT_GBP_POLICY_MAC);
+ }
+ else
+ {
+ gbp_endpoint_add_itf (gbp_itf_get_sw_if_index (gef->gef_itf), gei);
+ gbp_itf_l2_set_output_feature (gef->gef_itf,
+ L2OUTPUT_FEAT_GBP_POLICY_PORT);
+ }
+ l2fib_add_entry (ge->ge_key.gek_mac.bytes,
+ gbd->gb_bd_index,
+ gbp_itf_get_sw_if_index (gef->gef_itf),
+ L2FIB_ENTRY_RESULT_FLAG_STATIC);
+ }
+
+ vec_foreach (pfx, ge->ge_key.gek_ips)
+ {
+ ethernet_header_t *eth;
+ u32 ip_sw_if_index;
+ u32 fib_index;
+ u8 *rewrite;
+ index_t ai;
+
+ rewrite = NULL;
+ grd = gbp_route_domain_get (ge->ge_key.gek_grd);
+ fib_index = grd->grd_fib_index[pfx->fp_proto];
+ gef->gef_fib_index = fib_index;
+
+ bd_add_del_ip_mac (gbd->gb_bd_index, fib_proto_to_ip46 (pfx->fp_proto),
+ &pfx->fp_addr, &ge->ge_key.gek_mac, 1);
+
+ /*
+ * add a host route via the EPG's BVI we need this because the
+ * adj fib does not install, due to cover refinement check, since
+ * the BVI's prefix is /32
+ */
+ vec_validate (rewrite, sizeof (*eth) - 1);
+ eth = (ethernet_header_t *) rewrite;
+
+ eth->type = clib_host_to_net_u16 ((pfx->fp_proto == FIB_PROTOCOL_IP4 ?
+ ETHERNET_TYPE_IP4 :
+ ETHERNET_TYPE_IP6));
+
+ if (gbp_endpoint_is_remote (ge))
+ {
+ /*
+ * for dynamic EPs we must add the IP adjacency via the learned
+ * tunnel since the BD will not contain the EP's MAC since it was
+ * L3 learned. The dst MAC address used is the 'BD's MAC'.
+ */
+ ip_sw_if_index = gbp_itf_get_sw_if_index (gef->gef_itf);
+
+ mac_address_to_bytes (gbp_route_domain_get_local_mac (),
+ eth->src_address);
+ mac_address_to_bytes (gbp_route_domain_get_remote_mac (),
+ eth->dst_address);
+ }
+ else
+ {
+ /*
+ * for the static EPs we add the IP adjacency via the BVI
+ * knowing that the BD has the MAC address to route to and
+ * that policy will be applied on egress to the EP's port
+ */
+ ip_sw_if_index = gbd->gb_bvi_sw_if_index;
+
+ clib_memcpy (eth->src_address,
+ vnet_sw_interface_get_hw_address (vnet_get_main (),
+ ip_sw_if_index),
+ sizeof (eth->src_address));
+ mac_address_to_bytes (&ge->ge_key.gek_mac, eth->dst_address);
+ }
+
+ fib_table_entry_path_add (fib_index, pfx,
+ gbp_fib_source_low,
+ FIB_ENTRY_FLAG_NONE,
+ fib_proto_to_dpo (pfx->fp_proto),
+ &pfx->fp_addr, ip_sw_if_index,
+ ~0, 1, NULL, FIB_ROUTE_PATH_FLAG_NONE);
+
+ ai = adj_nbr_add_or_lock_w_rewrite (pfx->fp_proto,
+ fib_proto_to_link (pfx->fp_proto),
+ &pfx->fp_addr,
+ ip_sw_if_index, rewrite);
+ vec_add1 (gef->gef_adjs, ai);
+
+ /*
+ * if the endpoint is external then routed packet to it must be
+ * classifed to the BD's EPG. but this will happen anyway with
+ * the GBP_MAC classification.
+ */
+
+ if (NULL != gg)
+ {
+ if (gbp_endpoint_is_remote (ge))
+ {
+ dpo_id_t policy_dpo = DPO_INVALID;
+
+ /*
+ * interpose a policy DPO from the endpoint so that policy
+ * is applied
+ */
+ gbp_policy_dpo_add_or_lock (fib_proto_to_dpo (pfx->fp_proto),
+ grd->grd_scope,
+ gg->gg_sclass, ~0, &policy_dpo);
+
+ fib_table_entry_special_dpo_add (fib_index, pfx,
+ gbp_fib_source_hi,
+ FIB_ENTRY_FLAG_INTERPOSE,
+ &policy_dpo);
+ dpo_reset (&policy_dpo);
+ }
+
+ /*
+ * send a gratuitous ARP on the EPG's uplink. this is done so
+ * that if this EP has moved from some other place in the
+ * 'fabric', upstream devices are informed
+ */
+ if (gbp_endpoint_is_local (ge) && ~0 != gg->gg_uplink_sw_if_index)
+ {
+ gbp_endpoint_add_itf (gbp_itf_get_sw_if_index (gef->gef_itf),
+ gei);
+ ip_neighbor_advertise (vlib_get_main (),
+ (FIB_PROTOCOL_IP4 == pfx->fp_proto ?
+ IP46_TYPE_IP4 :
+ IP46_TYPE_IP6),
+ &pfx->fp_addr, gg->gg_uplink_sw_if_index);
+ }
+ }
+ }
+
+ if (gbp_endpoint_is_external (ge))
+ {
+ gbp_itf_l2_set_input_feature (gef->gef_itf,
+ L2INPUT_FEAT_GBP_LPM_CLASSIFY);
+ }
+ else if (gbp_endpoint_is_local (ge))
+ {
+ /*
+ * non-remote endpoints (i.e. those not arriving on iVXLAN
+ * tunnels) need to be classifed based on the the input interface.
+ * We enable the GBP-FWD feature only if the group has an uplink
+ * interface (on which the GBP-FWD feature would send UU traffic).
+ * External endpoints get classified based on an LPM match
+ */
+ l2input_feat_masks_t feats = L2INPUT_FEAT_GBP_SRC_CLASSIFY;
+
+ if (NULL != gg && ~0 != gg->gg_uplink_sw_if_index)
+ feats |= L2INPUT_FEAT_GBP_FWD;
+ gbp_itf_l2_set_input_feature (gef->gef_itf, feats);
+ }
+
+ /*
+ * update children with the new forwarding info
+ */
+ fib_node_back_walk_ctx_t bw_ctx = {
+ .fnbw_reason = FIB_NODE_BW_REASON_FLAG_EVALUATE,
+ .fnbw_flags = FIB_NODE_BW_FLAG_FORCE_SYNC,
+ };
+
+ fib_walk_sync (gbp_endpoint_fib_type, gei, &bw_ctx);
+}
+
+int
+gbp_endpoint_update_and_lock (gbp_endpoint_src_t src,
+ u32 sw_if_index,
+ const ip46_address_t * ips,
+ const mac_address_t * mac,
+ index_t gbdi, index_t grdi,
+ sclass_t sclass,
+ gbp_endpoint_flags_t flags,
+ const ip46_address_t * tun_src,
+ const ip46_address_t * tun_dst, u32 * handle)
+{
+ gbp_bridge_domain_t *gbd;
+ gbp_endpoint_group_t *gg;
+ gbp_endpoint_src_t best;
+ gbp_route_domain_t *grd;
+ gbp_endpoint_loc_t *gel;
+ gbp_endpoint_t *ge;
+ index_t ggi, gei;
+ int rv;
+
+ if (~0 == sw_if_index)
+ return (VNET_API_ERROR_INVALID_SW_IF_INDEX);
+
+ ge = NULL;
+ gg = NULL;