summaryrefslogtreecommitdiffstats
path: root/src/plugins/urpf/urpf_api.c
blob: ad060399347a1da2d583b5e27f3d4d44c198fb80 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
/*
 * 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 <urpf/urpf.h>
#include <vnet/plugin/plugin.h>
#include <vnet/ip/ip_types_api.h>

#include <vpp/app/version.h>

#include <vlibapi/api.h>
#include <vlibmemory/api.h>

/* define message IDs */
#include <vnet/format_fns.h>
#include <urpf/urpf.api_enum.h>
#include <urpf/urpf.api_types.h>

/**
 * Base message ID fot the plugin
 */
static u32 urpf_base_msg_id;
#define REPLY_MSG_ID_BASE urpf_base_msg_id

#include <vlibapi/api_helper_macros.h>

static int
urpf_mode_decode (vl_api_urpf_mode_t in, urpf_mode_t * out)
{
  if (0)
    ;
#define _(a,b)                                  \
  else if (URPF_API_MODE_##a == in)             \
    {                                           \
      *out = URPF_MODE_##a;                     \
      return (0);                               \
    }
  foreach_urpf_mode
#undef _
    return (VNET_API_ERROR_INVALID_VALUE);
}

static void
vl_api_urpf_update_t_handler (vl_api_urpf_update_t * mp)
{
  vl_api_urpf_update_reply_t *rmp;
  ip_address_family_t af;
  urpf_mode_t mode;
  int rv = 0;

  VALIDATE_SW_IF_INDEX (mp);

  rv = urpf_mode_decode (mp->mode, &mode);

  if (rv)
    goto done;

  rv = ip_address_family_decode (mp->af, &af);

  if (rv)
    goto done;

  urpf_update (mode, htonl (mp->sw_if_index), af,
	       (mp->is_input ? VLIB_RX : VLIB_TX));

  BAD_SW_IF_INDEX_LABEL;
done:
  REPLY_MACRO (VL_API_URPF_UPDATE_REPLY);
}

#include <urpf/urpf.api.c>

static clib_error_t *
urpf_api_init (vlib_main_t * vm)
{
  /* Ask for a correctly-sized block of API message decode slots */
  urpf_base_msg_id = setup_message_id_table ();

  return 0;
}

VLIB_INIT_FUNCTION (urpf_api_init);

/* *INDENT-OFF* */
VLIB_PLUGIN_REGISTER () = {
    .version = VPP_BUILD_VER,
    .description = "Unicast Reverse Path Forwarding (uRPF)",
};
/* *INDENT-ON* */

/*
 * fd.io coding-style-patch-verification: ON
 *
 * Local Variables:
 * eval: (c-set-style "gnu")
 * End:
 */
.sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */ .highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */ .highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */ .highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */ .highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */ .highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */ .highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */ .highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */ .highlight .vc { color: #336699 } /* Name.Variable.Class */ .highlight .vg { color: #dd7700 } /* Name.Variable.Global */ .highlight .vi { color: #3333bb } /* Name.Variable.Instance */ .highlight .vm { color: #336699 } /* Name.Variable.Magic */ .highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */ }
/*
 *------------------------------------------------------------------
 * Copyright (c) 2018 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 <vlib/vlib.h>
#include <vnet/mfib/mfib_entry.h>
#include <vnet/mfib/mfib_table.h>

#include <igmp/igmp_proxy.h>
#include <igmp/igmp.h>
#include <igmp/igmp_pkt.h>

void
igmp_proxy_device_mfib_path_add_del (igmp_group_t * group, u8 add)
{
  igmp_config_t *config;
  u32 mfib_index;

  config = igmp_config_get (group->config);
  mfib_index =
    mfib_table_get_index_for_sw_if_index (FIB_PROTOCOL_IP4,
					  config->sw_if_index);

  /* *INDENT-OFF* */
  mfib_prefix_t mpfx_group_addr = {
      .fp_proto = FIB_PROTOCOL_IP4,
      .fp_len = 32,
      .fp_grp_addr = {
	.ip4 = (*group->key).ip4,
      },
    };
  fib_route_path_t via_itf_path =
    {
      .frp_proto = fib_proto_to_dpo (FIB_PROTOCOL_IP4),
      .frp_addr = zero_addr,
      .frp_sw_if_index = config->sw_if_index,
      .frp_fib_index = 0,
      .frp_weight = 1,
      .frp_mitf_flags = MFIB_ITF_FLAG_FORWARD,
    };
  /* *INDENT-ON* */

  if (add)
    mfib_table_entry_path_update (mfib_index, &mpfx_group_addr,
				  MFIB_SOURCE_IGMP, &via_itf_path);
  else
    mfib_table_entry_path_remove (mfib_index, &mpfx_group_addr,
				  MFIB_SOURCE_IGMP, &via_itf_path);
}

igmp_proxy_device_t *
igmp_proxy_device_lookup (u32 vrf_id)
{
  igmp_main_t *im = &igmp_main;

  if (vec_len (im->igmp_proxy_device_by_vrf_id) > vrf_id)
    {
      u32 index;
      index = im->igmp_proxy_device_by_vrf_id[vrf_id];
      if (index != ~0)
	return (vec_elt_at_index (im->proxy_devices, index));
    }
  return NULL;
}

int
igmp_proxy_device_add_del (u32 vrf_id, u32 sw_if_index, u8 add)
{
  igmp_main_t *im = &igmp_main;
  igmp_proxy_device_t *proxy_device;
  igmp_config_t *config;
  u32 mfib_index;

  /* check VRF id */
  mfib_index =
    mfib_table_get_index_for_sw_if_index (FIB_PROTOCOL_IP4, sw_if_index);
  if (mfib_index == ~0)
    return VNET_API_ERROR_INVALID_INTERFACE;
  if (vrf_id != mfib_table_get (mfib_index, FIB_PROTOCOL_IP4)->mft_table_id)
    return VNET_API_ERROR_INVALID_INTERFACE;

  /* check IGMP configuration */
  config = igmp_config_lookup (sw_if_index);
  if (!config)
    return VNET_API_ERROR_INVALID_INTERFACE;
  if (config->mode != IGMP_MODE_HOST)
    return VNET_API_ERROR_INVALID_INTERFACE;

  proxy_device = igmp_proxy_device_lookup (vrf_id);
  if (!proxy_device && add)
    {
      vec_validate_init_empty (im->igmp_proxy_device_by_vrf_id, vrf_id, ~0);
      pool_get (im->proxy_devices, proxy_device);
      im->igmp_proxy_device_by_vrf_id[vrf_id] =
	proxy_device - im->proxy_devices;
      clib_memset (proxy_device, 0, sizeof (igmp_proxy_device_t));
      proxy_device->vrf_id = vrf_id;
      proxy_device->upstream_if = sw_if_index;
      config->proxy_device_id = vrf_id;
      /* lock mfib table */
      mfib_table_lock (mfib_index, FIB_PROTOCOL_IP4, MFIB_SOURCE_IGMP);
    }
  else if (proxy_device && !add)
    {
      while (vec_len (proxy_device->downstream_ifs) > 0)
	{
	  igmp_proxy_device_add_del_interface (vrf_id,
					       proxy_device->downstream_ifs
					       [0], 0);
	}
      vec_free (proxy_device->downstream_ifs);
      proxy_device->downstream_ifs = NULL;
      im->igmp_proxy_device_by_vrf_id[vrf_id] = ~0;
      pool_put (im->proxy_devices, proxy_device);
      config->proxy_device_id = ~0;
      /* clear proxy database */
      igmp_clear_config (config);
      /* unlock mfib table */
      mfib_table_unlock (mfib_index, FIB_PROTOCOL_IP4, MFIB_SOURCE_IGMP);
    }
  else
    return -1;

  return 0;
}

int
igmp_proxy_device_add_del_interface (u32 vrf_id, u32 sw_if_index, u8 add)
{
  igmp_proxy_device_t *proxy_device;
  u32 index;
  u32 mfib_index;

  proxy_device = igmp_proxy_device_lookup (vrf_id);
  if (!proxy_device)
    return -1;

  /* check VRF id */
  mfib_index =
    mfib_table_get_index_for_sw_if_index (FIB_PROTOCOL_IP4, sw_if_index);
  if (mfib_index == ~0)
    return VNET_API_ERROR_INVALID_INTERFACE;
  if (vrf_id != mfib_table_get (mfib_index, FIB_PROTOCOL_IP4)->mft_table_id)
    return VNET_API_ERROR_INVALID_INTERFACE;

  /* check IGMP configuration */
  igmp_config_t *config;
  config = igmp_config_lookup (sw_if_index);
  if (!config)
    return VNET_API_ERROR_INVALID_INTERFACE;
  if (config->mode != IGMP_MODE_ROUTER)
    return VNET_API_ERROR_INVALID_INTERFACE;

  if (add)
    {
      if (proxy_device->downstream_ifs)
	{
	  index = vec_search (proxy_device->downstream_ifs, sw_if_index);
	  if (index != ~0)
	    return -1;
	}
      vec_add1 (proxy_device->downstream_ifs, sw_if_index);
      config->proxy_device_id = vrf_id;
    }
  else
    {
      if (!proxy_device->downstream_ifs)
	return -2;
      index = vec_search (proxy_device->downstream_ifs, sw_if_index);
      if (index == ~0)
	return -3;
      /* remove (S,G)s belonging to this interface from proxy database */
      igmp_proxy_device_merge_config (config, /* block */ 1);
      vec_del1 (proxy_device->downstream_ifs, index);
      config->proxy_device_id = ~0;
    }

  return 0;
}

void
igmp_proxy_device_block_src (igmp_config_t * config, igmp_group_t * group,
			     igmp_src_t * src)
{
  igmp_proxy_device_t *proxy_device;
  igmp_config_t *proxy_config;
  igmp_group_t *proxy_group;
  igmp_src_t *proxy_src;
  u8 *ref;

  proxy_device = igmp_proxy_device_lookup (config->proxy_device_id);
  if (!proxy_device)
    return;

  proxy_config = igmp_config_lookup (proxy_device->upstream_if);
  ASSERT (proxy_config);

  proxy_group = igmp_group_lookup (proxy_config, group->key);
  if (proxy_group == NULL)
    return;

  proxy_src = igmp_src_lookup (proxy_group, src->key);
  if (proxy_src == NULL)
    return;

  if (vec_len (proxy_src->referance_by_config_index) <= group->config)
    {
      IGMP_DBG ("proxy block src: invalid config %u", group->config);
      return;
    }
  proxy_src->referance_by_config_index[group->config] = 0;
  vec_foreach (ref, proxy_src->referance_by_config_index)
  {
    if ((*ref) > 0)
      return;
  }

  /* build "Block Old Sources" report */
  igmp_pkt_build_report_t br;
  ip46_address_t *srcaddrs = NULL;

  igmp_pkt_build_report_init (&br, proxy_config->sw_if_index);
  vec_add1 (srcaddrs, *proxy_src->key);
  igmp_pkt_report_v3_add_report (&br, proxy_group->key, srcaddrs,
				 IGMP_MEMBERSHIP_GROUP_block_old_sources);
  igmp_pkt_report_v3_send (&br);


  igmp_group_src_remove (proxy_group, proxy_src);
  igmp_src_free (proxy_src);

  if (igmp_group_n_srcs (proxy_group, IGMP_FILTER_MODE_INCLUDE) == 0)
    {
      igmp_proxy_device_mfib_path_add_del (proxy_group, 0);
      igmp_proxy_device_mfib_path_add_del (group, 0);
      igmp_group_clear (proxy_group);
    }
}

always_inline void
igmp_proxy_device_merge_src (igmp_group_t * proxy_group, igmp_src_t * src,
			     ip46_address_t ** srcaddrs, u8 block)
{
  igmp_src_t *proxy_src;
  u32 d_config;

  proxy_src = igmp_src_lookup (proxy_group, src->key);

  if (proxy_src == NULL)
    {
      if (block)
	return;
      /* store downstream config index */
      d_config = igmp_group_get (src->group)->config;

      proxy_src =
	igmp_src_alloc (igmp_group_index (proxy_group), src->key,
			IGMP_MODE_HOST);

      hash_set_mem (proxy_group->igmp_src_by_key
		    [proxy_group->router_filter_mode], proxy_src->key,
		    igmp_src_index (proxy_src));

      vec_validate_init_empty (proxy_src->referance_by_config_index, d_config,
			       0);
      proxy_src->referance_by_config_index[d_config] = 1;
      vec_add1 (*srcaddrs, *proxy_src->key);
    }
  else
    {
      if (block)
	{
	  d_config = igmp_group_get (src->group)->config;
	  if (vec_len (proxy_src->referance_by_config_index) <= d_config)
	    {
	      IGMP_DBG ("proxy block src: invalid config %u", d_config);
	      return;
	    }
	  proxy_src->referance_by_config_index[d_config] = 0;
	  u8 *ref;
	  vec_foreach (ref, proxy_src->referance_by_config_index)
	  {
	    if ((*ref) > 0)
	      return;
	  }

	  vec_add1 (*srcaddrs, *proxy_src->key);

	  igmp_group_src_remove (proxy_group, proxy_src);
	  igmp_src_free (proxy_src);

	  if (igmp_group_n_srcs (proxy_group, IGMP_FILTER_MODE_INCLUDE) == 0)
	    {
	      igmp_proxy_device_mfib_path_add_del (proxy_group, 0);
	      igmp_group_clear (proxy_group);
	    }
	  return;
	}
      d_config = igmp_group_get (src->group)->config;
      vec_validate (proxy_src->referance_by_config_index, d_config);
      proxy_src->referance_by_config_index[d_config] = 1;
      return;
    }
}

always_inline igmp_group_t *
igmp_proxy_device_merge_group (igmp_proxy_device_t * proxy_device,
			       igmp_group_t * group,
			       ip46_address_t ** srcaddrs, u8 block)
{
  igmp_config_t *proxy_config;
  igmp_group_t *proxy_group;
  igmp_src_t *src;

  proxy_config = igmp_config_lookup (proxy_device->upstream_if);
  ASSERT (proxy_config);

  proxy_group = igmp_group_lookup (proxy_config, group->key);
  if (!proxy_group)
    {
      if (block)
	return NULL;
      u32 tmp = igmp_group_index (group);
      proxy_group =
	igmp_group_alloc (proxy_config, group->key,
			  group->router_filter_mode);
      igmp_proxy_device_mfib_path_add_del (proxy_group, 1);
      group = igmp_group_get (tmp);
    }
  if (block)
    {
      igmp_proxy_device_mfib_path_add_del (group, 0);
    }

  /* *INDENT-OFF* */
  FOR_EACH_SRC (src, group, group->router_filter_mode,
    ({
      igmp_proxy_device_merge_src (proxy_group, src, srcaddrs, block);
    }));
  /* *INDENT-ON* */
  return proxy_group;
}

void
igmp_proxy_device_merge_config (igmp_config_t * config, u8 block)
{
  igmp_proxy_device_t *proxy_device;
  igmp_group_t *group;
  igmp_group_t *proxy_group;
  ip46_address_t *srcaddrs = NULL;
  igmp_pkt_build_report_t br;

  proxy_device = igmp_proxy_device_lookup (config->proxy_device_id);
  if (!proxy_device)
    return;

  igmp_pkt_build_report_init (&br, proxy_device->upstream_if);

  /* *INDENT-OFF* */
  FOR_EACH_GROUP(group, config,
    ({
      proxy_group = igmp_proxy_device_merge_group (proxy_device, group, &srcaddrs, block);

      if ((vec_len(srcaddrs) > 0) && proxy_group)
	{
	  igmp_pkt_report_v3_add_report (&br, proxy_group->key, srcaddrs,
					 block ? IGMP_MEMBERSHIP_GROUP_block_old_sources :
					 IGMP_MEMBERSHIP_GROUP_allow_new_sources);
	}
      vec_free (srcaddrs);
    }));
  /* *INDENT-ON* */

  igmp_pkt_report_v3_send (&br);

}

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