diff options
Diffstat (limited to 'src/plugins/igmp/igmp_pkt.c')
-rw-r--r-- | src/plugins/igmp/igmp_pkt.c | 534 |
1 files changed, 534 insertions, 0 deletions
diff --git a/src/plugins/igmp/igmp_pkt.c b/src/plugins/igmp/igmp_pkt.c new file mode 100644 index 00000000000..5dd829de712 --- /dev/null +++ b/src/plugins/igmp/igmp_pkt.c @@ -0,0 +1,534 @@ +/* + *------------------------------------------------------------------ + * 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 <igmp/igmp_pkt.h> + +static void +vlib_buffer_append (vlib_buffer_t * b, uword l) +{ + b->current_data += l; + b->current_length += l; +} + +static vlib_buffer_t * +igmp_pkt_get_buffer (igmp_pkt_build_t * bk) +{ + vlib_buffer_free_list_t *fl; + vlib_main_t *vm; + vlib_buffer_t *b; + u32 bi; + + vm = vlib_get_main (); + + if (vlib_buffer_alloc (vm, &bi, 1) != 1) + return (NULL); + + b = vlib_get_buffer (vm, bi); + fl = vlib_buffer_get_free_list (vm, VLIB_BUFFER_DEFAULT_FREE_LIST_INDEX); + vlib_buffer_init_for_free_list (b, fl); + VLIB_BUFFER_TRACE_TRAJECTORY_INIT (b); + + b->flags |= VNET_BUFFER_F_LOCALLY_ORIGINATED; + b->flags |= VLIB_BUFFER_IS_TRACED; + + /* clear out stale data */ + vnet_buffer (b)->sw_if_index[VLIB_RX] = ~0; + + /* + * save progress in the builder + */ + vec_add1 (bk->buffers, bi); + bk->n_avail = vnet_sw_interface_get_mtu (vnet_get_main (), + bk->sw_if_index, VNET_MTU_IP4); + + return (b); +} + +static vlib_buffer_t * +igmp_pkt_build_ip_header (igmp_pkt_build_t * bk, + igmp_msg_type_t msg_type, + const igmp_group_t * group) +{ + ip4_header_t *ip4; + vlib_buffer_t *b; + u8 *option; + + b = igmp_pkt_get_buffer (bk); + + if (NULL == b) + return (NULL); + + ip4 = vlib_buffer_get_current (b); + memset (ip4, 0, sizeof (ip4_header_t)); + ip4->ip_version_and_header_length = 0x46; + ip4->ttl = 1; + ip4->protocol = IP_PROTOCOL_IGMP; + ip4->tos = 0xc0; + + ip4_src_address_for_packet (&ip4_main.lookup_main, + bk->sw_if_index, &ip4->src_address); + + vlib_buffer_append (b, sizeof (*ip4)); + bk->n_avail -= sizeof (*ip4); + + switch (msg_type) + { + case IGMP_MSG_REPORT: + ip4->dst_address.as_u32 = IGMP_MEMBERSHIP_REPORT_ADDRESS; + break; + case IGMP_MSG_QUERY: + if (group != NULL) + clib_memcpy (&ip4->dst_address, &group->key->ip4, + sizeof (ip4_address_t)); + else + ip4->dst_address.as_u32 = IGMP_GENERAL_QUERY_ADDRESS; + break; + } + + /* add the router alert optnios */ + option = vlib_buffer_get_current (b); + option[0] = 0x80 | 20; // IP4_ROUTER_ALERT_OPTION; + option[1] = 4; // length + option[2] = option[3] = 0; + + vlib_buffer_append (b, 4); + bk->n_avail -= 4; + + return (b); +} + +static vlib_buffer_t * +igmp_pkt_build_report_v3 (igmp_pkt_build_report_t * br, + const igmp_group_t * group) +{ + igmp_membership_report_v3_t *report; + vlib_buffer_t *b; + + b = igmp_pkt_build_ip_header (&br->base, IGMP_MSG_REPORT, group); + + if (NULL == b) + return (NULL); + + report = vlib_buffer_get_current (b); + report->header.type = IGMP_TYPE_membership_report_v3; + report->header.code = 0; + report->header.checksum = 0; + report->unused = 0; + + vlib_buffer_append (b, sizeof (igmp_membership_report_v3_t)); + br->base.n_avail -= sizeof (igmp_membership_report_v3_t); + br->base.n_bytes += sizeof (igmp_membership_report_v3_t); + + return (b); +} + +static void +igmp_pkt_tx (igmp_pkt_build_t * bk) +{ + const igmp_config_t *config; + vlib_buffer_t *b; + vlib_main_t *vm; + vlib_frame_t *f; + u32 *to_next; + u32 ii; + + vm = vlib_get_main (); + config = igmp_config_lookup (bk->sw_if_index); + f = vlib_get_frame_to_node (vm, ip4_rewrite_mcast_node.index); + to_next = vlib_frame_vector_args (f); + + vec_foreach_index (ii, bk->buffers) + { + b = vlib_get_buffer (vm, bk->buffers[ii]); + vnet_buffer (b)->ip.adj_index[VLIB_TX] = config->adj_index; + to_next[ii] = bk->buffers[ii]; + f->n_vectors++; + } + + vlib_put_frame_to_node (vm, ip4_rewrite_mcast_node.index, f); + + IGMP_DBG (" ..tx: %U", format_vnet_sw_if_index_name, + vnet_get_main (), bk->sw_if_index); + + vec_free (bk->buffers); + bk->buffers = 0; +} + +static vlib_buffer_t * +igmp_pkt_build_report_get_active (igmp_pkt_build_report_t * br) +{ + if (NULL == br->base.buffers) + return (NULL); + + return (vlib_get_buffer (vlib_get_main (), + br->base.buffers[vec_len (br->base.buffers) - 1])); +} + +static void +igmp_pkt_build_report_bake (igmp_pkt_build_report_t * br) +{ + igmp_membership_report_v3_t *igmp; + ip4_header_t *ip4; + vlib_buffer_t *b; + + b = igmp_pkt_build_report_get_active (br); + + b->current_data = 0; + + ip4 = vlib_buffer_get_current (b); + igmp = (igmp_membership_report_v3_t *) (((u32 *) ip4) + 6); + + igmp->n_groups = clib_host_to_net_u16 (br->n_groups); + + igmp->header.checksum = + ~ip_csum_fold (ip_incremental_checksum (0, igmp, br->base.n_bytes)); + + ip4->length = clib_host_to_net_u16 (b->current_length); + ip4->checksum = ip4_header_checksum (ip4); + + br->base.n_bytes = br->base.n_avail = br->n_groups = 0; +} + +void +igmp_pkt_report_v3_send (igmp_pkt_build_report_t * br) +{ + if (NULL == br->base.buffers) + return; + + igmp_pkt_build_report_bake (br); + igmp_pkt_tx (&br->base); +} + +static u32 +igmp_pkt_report_v3_get_size (const igmp_group_t * group) +{ + ASSERT (IGMP_FILTER_MODE_INCLUDE == group->router_filter_mode); + + return ((hash_elts (group->igmp_src_by_key[IGMP_FILTER_MODE_INCLUDE]) * + sizeof (ip4_address_t)) + sizeof (igmp_membership_group_v3_t)); +} + +static igmp_membership_group_v3_t * +igmp_pkt_report_v3_append_group (igmp_pkt_build_report_t * br, + const ip46_address_t * grp, + igmp_membership_group_v3_type_t type) +{ + igmp_membership_group_v3_t *igmp_group; + vlib_buffer_t *b; + + b = igmp_pkt_build_report_get_active (br); + + if (br->base.n_avail < sizeof (igmp_membership_group_v3_t)) + { + igmp_pkt_build_report_bake (br); + b = igmp_pkt_build_report_v3 (br, NULL); + if (NULL == b) + return (NULL); + } + br->base.n_avail -= sizeof (igmp_membership_group_v3_t); + br->base.n_bytes += sizeof (igmp_membership_group_v3_t); + br->n_groups++; + br->n_srcs = 0; + + igmp_group = vlib_buffer_get_current (b); + vlib_buffer_append (b, sizeof (igmp_membership_group_v3_t)); + + igmp_group->type = type; + igmp_group->n_aux_u32s = 0; + igmp_group->n_src_addresses = 0; + igmp_group->group_address.as_u32 = grp->ip4.as_u32; + + return (igmp_group); +} + +/** + * 4.2.16 + " If the set of Group Records required in a Report does not fit within + * the size limit of a single Report message (as determined by the MTU + * of the network on which it will be sent), the Group Records are sent + * in as many Report messages as needed to report the entire set. + + * If a single Group Record contains so many source addresses that it + * does not fit within the size limit of a single Report message, if its + * Type is not MODE_IS_EXCLUDE or CHANGE_TO_EXCLUDE_MODE, it is split + * into multiple Group Records, each containing a different subset of + * the source addresses and each sent in a separate Report message. If + * its Type is MODE_IS_EXCLUDE or CHANGE_TO_EXCLUDE_MODE, a single Group + * Record is sent, containing as many source addresses as can fit, and + * the remaining source addresses are not reported; though the choice of + * which sources to report is arbitrary, it is preferable to report the + * same set of sources in each subsequent report, rather than reporting + * different sources each time." + */ +static igmp_membership_group_v3_t * +igmp_pkt_report_v3_append_src (igmp_pkt_build_report_t * br, + igmp_membership_group_v3_t * igmp_group, + const ip46_address_t * grp, + igmp_membership_group_v3_type_t type, + const ip46_address_t * src) +{ + vlib_buffer_t *b; + + b = igmp_pkt_build_report_get_active (br); + + if (br->base.n_avail < sizeof (ip4_address_t)) + { + igmp_group->n_src_addresses = clib_host_to_net_u16 (br->n_srcs); + igmp_pkt_build_report_bake (br); + b = igmp_pkt_build_report_v3 (br, NULL); + if (NULL == b) + return (NULL); + igmp_group = igmp_pkt_report_v3_append_group (br, grp, type); + } + + igmp_group->src_addresses[br->n_srcs].as_u32 = src->ip4.as_u32; + br->n_srcs++; + br->base.n_avail -= sizeof (ip4_address_t); + br->base.n_bytes += sizeof (ip4_address_t); + vlib_buffer_append (b, sizeof (ip4_address_t)); + + return (igmp_group); +} + +void +igmp_pkt_report_v3_add_report (igmp_pkt_build_report_t * br, + const ip46_address_t * grp, + const ip46_address_t * srcs, + igmp_membership_group_v3_type_t type) +{ + igmp_membership_group_v3_t *igmp_group; + const ip46_address_t *s; + vlib_buffer_t *b; + + b = igmp_pkt_build_report_get_active (br); + + if (NULL == b) + { + b = igmp_pkt_build_report_v3 (br, NULL); + if (NULL == b) + /* failed to allocate buffer */ + return; + } + + igmp_group = igmp_pkt_report_v3_append_group (br, grp, type); + + if (NULL == igmp_group) + return; + + /* *INDENT-OFF* */ + vec_foreach(s, srcs) + { + igmp_group = igmp_pkt_report_v3_append_src(br, igmp_group, + grp, type, s); + if (NULL == igmp_group) + return; + }; + /* *INDENT-ON* */ + + igmp_group->n_src_addresses = clib_host_to_net_u16 (br->n_srcs); + + IGMP_DBG (" ..add-group: %U", format_ip46_address, grp, IP46_TYPE_IP4); +} + +void +igmp_pkt_report_v3_add_group (igmp_pkt_build_report_t * br, + const igmp_group_t * group, + igmp_membership_group_v3_type_t type) +{ + igmp_membership_group_v3_t *igmp_group; + vlib_buffer_t *b; + igmp_src_t *src; + + b = igmp_pkt_build_report_get_active (br); + + if (NULL == b) + { + b = igmp_pkt_build_report_v3 (br, NULL); + if (NULL == b) + /* failed to allocate buffer */ + return; + } + + /* + * if the group won't fit in a partially full buffer, start again + */ + if ((0 != br->n_groups) && + (igmp_pkt_report_v3_get_size (group) > br->base.n_avail)) + { + igmp_pkt_build_report_bake (br); + b = igmp_pkt_build_report_v3 (br, NULL); + if (NULL == b) + /* failed to allocate buffer */ + return; + } + + igmp_group = igmp_pkt_report_v3_append_group (br, group->key, type); + + /* *INDENT-OFF* */ + FOR_EACH_SRC (src, group, IGMP_FILTER_MODE_INCLUDE, + ({ + igmp_group = igmp_pkt_report_v3_append_src(br, igmp_group, + group->key, type, + src->key); + if (NULL == igmp_group) + return; + })); + /* *INDENT-ON* */ + igmp_group->n_src_addresses = clib_host_to_net_u16 (br->n_srcs); + + IGMP_DBG (" ..add-group: %U srcs:%d", + format_igmp_key, group->key, + hash_elts (group->igmp_src_by_key[IGMP_FILTER_MODE_INCLUDE])); +} + +void +igmp_pkt_build_report_init (igmp_pkt_build_report_t * br, u32 sw_if_index) +{ + memset (br, 0, sizeof (*br)); + br->base.sw_if_index = sw_if_index; +} + +static vlib_buffer_t * +igmp_pkt_build_query_get_active (igmp_pkt_build_query_t * bq) +{ + if (NULL == bq->base.buffers) + return (NULL); + + return (vlib_get_buffer (vlib_get_main (), + bq->base.buffers[vec_len (bq->base.buffers) - 1])); +} + +static vlib_buffer_t * +igmp_pkt_build_query_v3 (igmp_pkt_build_query_t * bq, + const igmp_group_t * group) +{ + igmp_membership_query_v3_t *query; + vlib_buffer_t *b; + + b = igmp_pkt_build_ip_header (&bq->base, IGMP_MSG_QUERY, group); + + if (NULL == b) + return (NULL); + + query = vlib_buffer_get_current (b); + query->header.type = IGMP_TYPE_membership_query; + query->header.code = 0; + query->header.checksum = 0; + query->qqi_code = 0; + query->resv_s_qrv = 0; + + if (NULL != group) + query->group_address.as_u32 = group->key->ip4.as_u32; + else + query->group_address.as_u32 = 0; + + vlib_buffer_append (b, sizeof (igmp_membership_query_v3_t)); + bq->base.n_avail -= sizeof (igmp_membership_query_v3_t); + bq->base.n_bytes += sizeof (igmp_membership_query_v3_t); + + return (b); +} + +void +igmp_pkt_query_v3_add_group (igmp_pkt_build_query_t * bq, + const igmp_group_t * group, + const ip46_address_t * srcs) +{ + vlib_buffer_t *b; + + b = igmp_pkt_build_query_get_active (bq); + + if (NULL == b) + { + b = igmp_pkt_build_query_v3 (bq, group); + if (NULL == b) + /* failed to allocate buffer */ + return; + } + + if (NULL != srcs) + { + igmp_membership_query_v3_t *query; + const ip46_address_t *src; + + query = vlib_buffer_get_current (b); + + vec_foreach (src, srcs) + { + query->src_addresses[bq->n_srcs++].as_u32 = src->ip4.as_u32; + + vlib_buffer_append (b, sizeof (ip4_address_t)); + bq->base.n_bytes += sizeof (ip4_address_t); + bq->base.n_avail += sizeof (ip4_address_t); + } + } + /* + * else + * general query and we're done + */ +} + +static void +igmp_pkt_build_query_bake (igmp_pkt_build_query_t * bq) +{ + igmp_membership_query_v3_t *igmp; + ip4_header_t *ip4; + vlib_buffer_t *b; + + b = igmp_pkt_build_query_get_active (bq); + + b->current_data = 0; + + ip4 = vlib_buffer_get_current (b); + // account for options + igmp = (igmp_membership_query_v3_t *) (((u32 *) ip4) + 6); + + igmp->n_src_addresses = clib_host_to_net_u16 (bq->n_srcs); + + igmp->header.checksum = + ~ip_csum_fold (ip_incremental_checksum (0, igmp, bq->base.n_bytes)); + + ip4->length = clib_host_to_net_u16 (b->current_length); + ip4->checksum = ip4_header_checksum (ip4); + + bq->base.n_bytes = bq->base.n_avail = bq->n_srcs = 0; +} + +void +igmp_pkt_query_v3_send (igmp_pkt_build_query_t * bq) +{ + if (NULL == bq->base.buffers) + return; + + igmp_pkt_build_query_bake (bq); + igmp_pkt_tx (&bq->base); +} + +void +igmp_pkt_build_query_init (igmp_pkt_build_query_t * bq, u32 sw_if_index) +{ + memset (bq, 0, sizeof (*bq)); + bq->base.sw_if_index = sw_if_index; +} + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ |