From 54c6dc450031443663d40b836a8b0bffdcc2bdea Mon Sep 17 00:00:00 2001 From: Neale Ranns Date: Wed, 17 Jan 2018 10:29:10 -0800 Subject: [PATCH] For DHCP client configuration control the setting of the broadcast flag in the DISCOVER message sent. According to RFC2131: In the case of a client using DHCP for initial configuration (before the client's TCP/IP software has been completely configured), DHCP requires creative use of the client's TCP/IP software and liberal interpretation of RFC 1122. The TCP/IP software SHOULD accept and forward to the IP layer any IP packets delivered to the client's hardware address before the IP address is configured; DHCP servers and BOOTP relay agents may not be able to deliver DHCP messages to clients that cannot accept hardware unicast datagrams before the TCP/IP software is configured. To work around some clients that cannot accept IP unicast datagrams before the TCP/IP software is configured as discussed in the previous paragraph, DHCP uses the 'flags' field [21]. The leftmost bit is defined as the BROADCAST (B) flag. The semantics of this flag are discussed in section 4.1 of this document. The remaining bits of the flags field are reserved for future use. They MUST be set to zero by clients and ignored by servers and relay agents. Figure 2 gives the format of the 'flags' field. this changes means VPP conforms to the: "SHOULD accept and forward to the IP layer any IP packets delivered to the client's hardware address before the IP address is configured" with the caveat that VPP allows DHCP packets destined to the stanard client DHCP port to be delivered. With this enhancement the control-plane is now able to choose the setting of the broadcast flag. Change-Id: Ia4eb2c9bb1e30c29f9192facc645e9533641955a Signed-off-by: Neale Ranns --- src/vnet.am | 2 +- src/vnet/dhcp/client.c | 151 ++++++---------- src/vnet/dhcp/client.h | 8 +- src/vnet/dhcp/dhcp.api | 3 + src/vnet/dhcp/dhcp_api.c | 2 +- src/vnet/dhcp/dhcp_client_detect.c | 333 +++++++++++++++++++++++++++++++++++ src/vpp-api/vom/dhcp_config.cpp | 10 +- src/vpp-api/vom/dhcp_config.hpp | 12 +- src/vpp-api/vom/dhcp_config_cmds.cpp | 4 +- src/vpp-api/vom/dhcp_config_cmds.hpp | 8 +- test/test_dhcp.py | 109 +++++++++++- test/vpp_papi_provider.py | 2 + 12 files changed, 533 insertions(+), 111 deletions(-) create mode 100644 src/vnet/dhcp/dhcp_client_detect.c diff --git a/src/vnet.am b/src/vnet.am index 84407bc1861..32d3167b16e 100644 --- a/src/vnet.am +++ b/src/vnet.am @@ -724,7 +724,7 @@ API_FILES += vnet/lisp-gpe/lisp_gpe.api ######################################## libvnet_la_SOURCES += \ vnet/dhcp/client.c \ - vnet/dhcp/client.h \ + vnet/dhcp/dhcp_client_detect.c \ vnet/dhcp/dhcp_api.c nobase_include_HEADERS += \ diff --git a/src/vnet/dhcp/client.c b/src/vnet/dhcp/client.c index 8043bf22d43..03fc2689abf 100644 --- a/src/vnet/dhcp/client.c +++ b/src/vnet/dhcp/client.c @@ -21,56 +21,6 @@ dhcp_client_main_t dhcp_client_main; static u8 *format_dhcp_client_state (u8 * s, va_list * va); static vlib_node_registration_t dhcp_client_process_node; -static void -dhcp_client_add_rx_address (dhcp_client_main_t * dcm, dhcp_client_t * c) -{ - /* Install a local entry for the offered address */ - fib_prefix_t rx = { - .fp_len = 32, - .fp_addr.ip4 = c->leased_address, - .fp_proto = FIB_PROTOCOL_IP4, - }; - - fib_table_entry_special_add (fib_table_get_index_for_sw_if_index - (FIB_PROTOCOL_IP4, c->sw_if_index), &rx, - FIB_SOURCE_DHCP, (FIB_ENTRY_FLAG_LOCAL)); - - /* And add the server's address as uRPF exempt so we can accept - * local packets from it */ - fib_prefix_t server = { - .fp_len = 32, - .fp_addr.ip4 = c->dhcp_server, - .fp_proto = FIB_PROTOCOL_IP4, - }; - - fib_table_entry_special_add (fib_table_get_index_for_sw_if_index - (FIB_PROTOCOL_IP4, c->sw_if_index), &server, - FIB_SOURCE_URPF_EXEMPT, (FIB_ENTRY_FLAG_DROP)); -} - -static void -dhcp_client_remove_rx_address (dhcp_client_main_t * dcm, dhcp_client_t * c) -{ - fib_prefix_t rx = { - .fp_len = 32, - .fp_addr.ip4 = c->leased_address, - .fp_proto = FIB_PROTOCOL_IP4, - }; - - fib_table_entry_special_remove (fib_table_get_index_for_sw_if_index - (FIB_PROTOCOL_IP4, c->sw_if_index), &rx, - FIB_SOURCE_DHCP); - fib_prefix_t server = { - .fp_len = 32, - .fp_addr.ip4 = c->dhcp_server, - .fp_proto = FIB_PROTOCOL_IP4, - }; - - fib_table_entry_special_remove (fib_table_get_index_for_sw_if_index - (FIB_PROTOCOL_IP4, c->sw_if_index), &server, - FIB_SOURCE_URPF_EXEMPT); -} - static void dhcp_client_acquire_address (dhcp_client_main_t * dcm, dhcp_client_t * c) { @@ -233,13 +183,6 @@ dhcp_client_for_us (u32 bi, vlib_buffer_t * b, c->next_transmit = now + 5.0; break; } - /* - * in order to accept unicasted ACKs we need to configure the offered - * address on the interface. However, at this point we may not know the - * subnet-mask (an OFFER may not contain it). So add a temporary receice - * and uRPF excempt entry - */ - dhcp_client_add_rx_address (dcm, c); /* Received an offer, go send a request */ c->state = DHCP_REQUEST; @@ -267,9 +210,11 @@ dhcp_client_for_us (u32 bi, vlib_buffer_t * b, void (*fp) (u32, u32, u8 *, u8, u8, u8 *, u8 *, u8 *) = c->event_callback; - /* replace the temporary RX address with the correct subnet */ - dhcp_client_remove_rx_address (dcm, c); + /* add the advertised subnet and disable the feature */ dhcp_client_acquire_address (dcm, c); + vnet_feature_enable_disable ("ip4-unicast", + "ip4-dhcp-client-detect", + c->sw_if_index, 0, 0, 0); /* * Configure default IP route: @@ -285,8 +230,19 @@ dhcp_client_for_us (u32 bi, vlib_buffer_t * b, .ip4 = c->router_address, }; - fib_table_entry_path_add (fib_table_get_index_for_sw_if_index (FIB_PROTOCOL_IP4, c->sw_if_index), &all_0s, FIB_SOURCE_DHCP, FIB_ENTRY_FLAG_NONE, DPO_PROTO_IP4, &nh, c->sw_if_index, ~0, 1, NULL, // no label stack - FIB_ROUTE_PATH_FLAG_NONE); + /* *INDENT-OFF* */ + fib_table_entry_path_add ( + fib_table_get_index_for_sw_if_index ( + FIB_PROTOCOL_IP4, + c->sw_if_index), + &all_0s, + FIB_SOURCE_DHCP, + FIB_ENTRY_FLAG_NONE, + DPO_PROTO_IP4, + &nh, c->sw_if_index, + ~0, 1, NULL, // no label stack + FIB_ROUTE_PATH_FLAG_NONE); + /* *INDENT-ON* */ } /* @@ -418,7 +374,9 @@ send_dhcp_pkt (dhcp_client_main_t * dcm, dhcp_client_t * c, dhcp->hardware_type = 1; /* ethernet */ dhcp->hardware_address_length = 6; dhcp->transaction_identifier = c->transaction_id; - dhcp->flags = clib_host_to_net_u16 (is_broadcast ? DHCP_FLAG_BROADCAST : 0); + dhcp->flags = + clib_host_to_net_u16 (is_broadcast && c->set_broadcast_flag ? + DHCP_FLAG_BROADCAST : 0); dhcp->magic_cookie.as_u32 = DHCP_MAGIC; o = (dhcp_option_t *) dhcp->options; @@ -676,14 +634,13 @@ dhcp_client_process (vlib_main_t * vm, break; case ~0: - pool_foreach (c, dcm->clients, ( - { - timeout = - dhcp_client_sm (now, timeout, - (uword) (c - - dcm->clients)); - } - )); + /* *INDENT-OFF* */ + pool_foreach (c, dcm->clients, + ({ + timeout = dhcp_client_sm (now, timeout, + (uword) (c - dcm->clients)); + })); + /* *INDENT-ON* */ if (pool_elts (dcm->clients) == 0) timeout = 100.0; break; @@ -785,13 +742,14 @@ show_dhcp_client_command_fn (vlib_main_t * vm, return 0; } - pool_foreach (c, dcm->clients, ( - { - vlib_cli_output (vm, "%U", - format_dhcp_client, dcm, - c, verbose); - } - )); + /* *INDENT-OFF* */ + pool_foreach (c, dcm->clients, + ({ + vlib_cli_output (vm, "%U", + format_dhcp_client, dcm, + c, verbose); + })); + /* *INDENT-ON* */ return 0; } @@ -812,11 +770,6 @@ dhcp_client_add_del (dhcp_client_add_del_args_t * a) vlib_main_t *vm = dcm->vlib_main; dhcp_client_t *c; uword *p; - fib_prefix_t all_1s = { - .fp_len = 32, - .fp_addr.ip4.as_u32 = 0xffffffff, - .fp_proto = FIB_PROTOCOL_IP4, - }; fib_prefix_t all_0s = { .fp_len = 0, .fp_addr.ip4.as_u32 = 0x0, @@ -840,6 +793,7 @@ dhcp_client_add_del (dhcp_client_add_del_args_t * a) c->option_55_data = a->option_55_data; c->hostname = a->hostname; c->client_identifier = a->client_identifier; + c->set_broadcast_flag = a->set_broadcast_flag; do { c->transaction_id = random_u32 (&dcm->seed); @@ -848,17 +802,18 @@ dhcp_client_add_del (dhcp_client_add_del_args_t * a) set_l2_rewrite (dcm, c); hash_set (dcm->client_by_sw_if_index, a->sw_if_index, c - dcm->clients); - /* this add is ref counted by FIB so we can add for each itf */ - fib_table_entry_special_add (fib_table_get_index_for_sw_if_index - (FIB_PROTOCOL_IP4, c->sw_if_index), - &all_1s, FIB_SOURCE_DHCP, - FIB_ENTRY_FLAG_LOCAL); - /* - * enable the interface to RX IPv4 packets - * this is also ref counted + * In order to accept any OFFER, whether broadcasted or unicasted, we + * need to configure the dhcp-client-detect feature as an input feature + * so the DHCP OFFER is sent to the ip4-local node. Without this a + * broadcasted OFFER hits the 255.255.255.255/32 address and a unicast + * hits 0.0.0.0/0 both of which default to drop and the latter may forward + * of box - not what we want. Nor to we want to change these route for + * all interfaces in this table */ - ip4_sw_interface_enable_disable (c->sw_if_index, 1); + vnet_feature_enable_disable ("ip4-unicast", + "ip4-dhcp-client-detect", + c->sw_if_index, 1, 0, 0); vlib_process_signal_event (vm, dhcp_client_process_node.index, EVENT_DHCP_CLIENT_WAKEUP, c - dcm->clients); @@ -867,10 +822,6 @@ dhcp_client_add_del (dhcp_client_add_del_args_t * a) { c = pool_elt_at_index (dcm->clients, p[0]); - fib_table_entry_special_remove (fib_table_get_index_for_sw_if_index - (FIB_PROTOCOL_IP4, c->sw_if_index), - &all_1s, FIB_SOURCE_DHCP); - if (c->router_address.as_u32) { ip46_address_t nh = { @@ -883,9 +834,7 @@ dhcp_client_add_del (dhcp_client_add_del_args_t * a) DPO_PROTO_IP4, &nh, c->sw_if_index, ~0, 1, FIB_ROUTE_PATH_FLAG_NONE); } - dhcp_client_remove_rx_address (dcm, c); dhcp_client_release_address (dcm, c); - ip4_sw_interface_enable_disable (c->sw_if_index, 0); vec_free (c->option_55_data); vec_free (c->hostname); @@ -903,7 +852,8 @@ dhcp_client_config (vlib_main_t * vm, u8 * hostname, u8 * client_id, u32 is_add, - u32 client_index, void *event_callback, u32 pid) + u32 client_index, + void *event_callback, u8 set_broadcast_flag, u32 pid) { dhcp_client_add_del_args_t _a, *a = &_a; int rv; @@ -914,6 +864,7 @@ dhcp_client_config (vlib_main_t * vm, a->client_index = client_index; a->pid = pid; a->event_callback = event_callback; + a->set_broadcast_flag = set_broadcast_flag; vec_validate (a->hostname, strlen ((char *) hostname) - 1); strncpy ((char *) a->hostname, (char *) hostname, vec_len (a->hostname)); vec_validate (a->client_identifier, strlen ((char *) client_id) - 1); @@ -990,6 +941,7 @@ dhcp_client_set_command_fn (vlib_main_t * vm, u32 sw_if_index; u8 *hostname = 0; u8 sw_if_index_set = 0; + u8 set_broadcast_flag = 1; int is_add = 1; dhcp_client_add_del_args_t _a, *a = &_a; int rv; @@ -1003,6 +955,8 @@ dhcp_client_set_command_fn (vlib_main_t * vm, ; else if (unformat (input, "del")) is_add = 0; + else if (unformat (input, "broadcast", &set_broadcast_flag)) + is_add = 0; else break; } @@ -1015,6 +969,7 @@ dhcp_client_set_command_fn (vlib_main_t * vm, a->sw_if_index = sw_if_index; a->hostname = hostname; a->client_identifier = format (0, "vpe 1.0%c", 0); + a->set_broadcast_flag = set_broadcast_flag; /* * Option 55 request list. These data precisely match diff --git a/src/vnet/dhcp/client.h b/src/vnet/dhcp/client.h index d9c7e25d0b2..1c2becb3058 100644 --- a/src/vnet/dhcp/client.h +++ b/src/vnet/dhcp/client.h @@ -71,6 +71,10 @@ typedef struct /* Information used for event callback */ u32 client_index; u32 pid; + + /* Set the broadcast Flag in the Discover/Request messages */ + u8 set_broadcast_flag; + void *event_callback; } dhcp_client_t; @@ -90,6 +94,7 @@ typedef struct { int is_add; u32 sw_if_index; + u8 set_broadcast_flag; /* vectors, consumed by dhcp client code */ u8 *hostname; @@ -118,7 +123,8 @@ int dhcp_client_config (vlib_main_t * vm, u8 * hostname, u8 * client_id, u32 is_add, - u32 client_index, void *event_callback, u32 pid); + u32 client_index, + void *event_callback, u8 set_broadcast_flag, u32 pid); #endif /* included_dhcp_client_h */ diff --git a/src/vnet/dhcp/dhcp.api b/src/vnet/dhcp/dhcp.api index 528915a46e9..721a1be3547 100644 --- a/src/vnet/dhcp/dhcp.api +++ b/src/vnet/dhcp/dhcp.api @@ -71,6 +71,8 @@ autoreply define dhcp_proxy_set_vss @param is_add - add the config if non-zero, else delete @param want_dhcp_event - DHCP event sent to the sender via dhcp_compl_event API message if non-zero + @param set_broadcast_flag - in the DHCP Discover to control + how the resulting OFFER is addressed. @param pid - sender's pid */ autoreply define dhcp_client_config @@ -82,6 +84,7 @@ autoreply define dhcp_client_config u8 client_id[64]; u8 is_add; u8 want_dhcp_event; + u8 set_broadcast_flag; u32 pid; }; diff --git a/src/vnet/dhcp/dhcp_api.c b/src/vnet/dhcp/dhcp_api.c index 8e210cdd5b0..401f6b75edc 100644 --- a/src/vnet/dhcp/dhcp_api.c +++ b/src/vnet/dhcp/dhcp_api.c @@ -248,7 +248,7 @@ static void vl_api_dhcp_client_config_t_handler mp->hostname, mp->client_id, mp->is_add, mp->client_index, mp->want_dhcp_event ? dhcp_compl_event_callback : - NULL, mp->pid); + NULL, mp->set_broadcast_flag, mp->pid); BAD_SW_IF_INDEX_LABEL; diff --git a/src/vnet/dhcp/dhcp_client_detect.c b/src/vnet/dhcp/dhcp_client_detect.c new file mode 100644 index 00000000000..1b916cdd356 --- /dev/null +++ b/src/vnet/dhcp/dhcp_client_detect.c @@ -0,0 +1,333 @@ +/* + * DHCP feature; applied as an input feature to select DHCP packets + * + * Copyright (c) 2013 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 + +#define foreach_dhcp_client_detect \ + _(EXTRACT, "Extract") + +typedef enum +{ +#define _(sym,str) DHCP_CLIENT_DETECT_ERROR_##sym, + foreach_dhcp_client_detect +#undef _ + DHCP_CLIENT_DETECT_N_ERROR, +} dhcp_client_detect_error_t; + +static char *dhcp_client_detect_error_strings[] = { +#define _(sym,string) string, + foreach_dhcp_client_detect +#undef _ +}; + +typedef enum +{ +#define _(sym,str) DHCP_CLIENT_DETECT_NEXT_##sym, + foreach_dhcp_client_detect +#undef _ + DHCP_CLIENT_DETECT_N_NEXT, +} dhcp_client_detect_next_t; + +/** + * per-packet trace data + */ +typedef struct dhcp_client_detect_trace_t_ +{ + /* per-pkt trace data */ + u8 extracted; +} dhcp_client_detect_trace_t; + +static uword +dhcp_client_detect_node_fn (vlib_main_t * vm, + vlib_node_runtime_t * node, vlib_frame_t * frame) +{ + dhcp_client_detect_next_t next_index; + u16 dhcp_client_port_network_order; + u32 n_left_from, *from, *to_next; + u32 extractions; + + dhcp_client_port_network_order = + clib_net_to_host_u16 (UDP_DST_PORT_dhcp_to_client); + next_index = 0; + extractions = 0; + n_left_from = frame->n_vectors; + from = vlib_frame_vector_args (frame); + + while (n_left_from > 0) + { + u32 n_left_to_next; + + vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next); + + /* + * This loop is optimised not so we can really quickly process DHCp + * offers... but so we can quickly sift them out when the interface + * is also receving 'normal' packets + */ + while (n_left_from >= 8 && n_left_to_next >= 4) + { + udp_header_t *udp0, *udp1, *udp2, *udp3; + ip4_header_t *ip0, *ip1, *ip2, *ip3; + vlib_buffer_t *b0, *b1, *b2, *b3; + u32 next0, next1, next2, next3; + u32 bi0, bi1, bi2, bi3; + + next0 = next1 = next2 = next3 = ~0; + bi0 = to_next[0] = from[0]; + bi1 = to_next[1] = from[1]; + bi2 = to_next[2] = from[2]; + bi3 = to_next[3] = from[3]; + + /* Prefetch next iteration. */ + { + vlib_buffer_t *p2, *p3, *p4, *p5; + + p2 = vlib_get_buffer (vm, from[2]); + p3 = vlib_get_buffer (vm, from[3]); + p4 = vlib_get_buffer (vm, from[4]); + p5 = vlib_get_buffer (vm, from[5]); + + vlib_prefetch_buffer_header (p2, STORE); + vlib_prefetch_buffer_header (p3, STORE); + vlib_prefetch_buffer_header (p4, STORE); + vlib_prefetch_buffer_header (p5, STORE); + + CLIB_PREFETCH (p2->data, sizeof (ip0[0]) + sizeof (udp0[0]), + STORE); + CLIB_PREFETCH (p3->data, sizeof (ip0[0]) + sizeof (udp0[0]), + STORE); + CLIB_PREFETCH (p4->data, sizeof (ip0[0]) + sizeof (udp0[0]), + STORE); + CLIB_PREFETCH (p5->data, sizeof (ip0[0]) + sizeof (udp0[0]), + STORE); + } + + from += 4; + to_next += 4; + n_left_from -= 4; + n_left_to_next -= 4; + + b0 = vlib_get_buffer (vm, bi0); + b1 = vlib_get_buffer (vm, bi1); + b2 = vlib_get_buffer (vm, bi2); + b3 = vlib_get_buffer (vm, bi3); + ip0 = vlib_buffer_get_current (b0); + ip1 = vlib_buffer_get_current (b1); + ip2 = vlib_buffer_get_current (b2); + ip3 = vlib_buffer_get_current (b2); + + vnet_feature_next (vnet_buffer (b0)->sw_if_index[VLIB_TX], + &next0, b0); + vnet_feature_next (vnet_buffer (b1)->sw_if_index[VLIB_TX], + &next1, b1); + vnet_feature_next (vnet_buffer (b2)->sw_if_index[VLIB_TX], + &next2, b2); + vnet_feature_next (vnet_buffer (b3)->sw_if_index[VLIB_TX], + &next3, b3); + + if (ip0->protocol == IP_PROTOCOL_UDP) + { + udp0 = (udp_header_t *) (ip0 + 1); + + if (dhcp_client_port_network_order == udp0->dst_port) + { + next0 = DHCP_CLIENT_DETECT_NEXT_EXTRACT; + extractions++; + } + } + if (ip1->protocol == IP_PROTOCOL_UDP) + { + udp1 = (udp_header_t *) (ip1 + 1); + + if (dhcp_client_port_network_order == udp1->dst_port) + { + next1 = DHCP_CLIENT_DETECT_NEXT_EXTRACT; + extractions++; + } + } + if (ip2->protocol == IP_PROTOCOL_UDP) + { + udp2 = (udp_header_t *) (ip2 + 1); + + if (dhcp_client_port_network_order == udp2->dst_port) + { + next2 = DHCP_CLIENT_DETECT_NEXT_EXTRACT; + extractions++; + } + } + if (ip3->protocol == IP_PROTOCOL_UDP) + { + udp3 = (udp_header_t *) (ip3 + 1); + + if (dhcp_client_port_network_order == udp3->dst_port) + { + next3 = DHCP_CLIENT_DETECT_NEXT_EXTRACT; + extractions++; + } + } + + if (PREDICT_FALSE (b0->flags & VLIB_BUFFER_IS_TRACED)) + { + dhcp_client_detect_trace_t *t = + vlib_add_trace (vm, node, b0, sizeof (*t)); + t->extracted = (next0 == DHCP_CLIENT_DETECT_NEXT_EXTRACT); + } + if (PREDICT_FALSE (b1->flags & VLIB_BUFFER_IS_TRACED)) + { + dhcp_client_detect_trace_t *t = + vlib_add_trace (vm, node, b1, sizeof (*t)); + t->extracted = (next1 == DHCP_CLIENT_DETECT_NEXT_EXTRACT); + } + if (PREDICT_FALSE (b2->flags & VLIB_BUFFER_IS_TRACED)) + { + dhcp_client_detect_trace_t *t = + vlib_add_trace (vm, node, b2, sizeof (*t)); + t->extracted = (next2 == DHCP_CLIENT_DETECT_NEXT_EXTRACT); + } + if (PREDICT_FALSE (b3->flags & VLIB_BUFFER_IS_TRACED)) + { + dhcp_client_detect_trace_t *t = + vlib_add_trace (vm, node, b3, sizeof (*t)); + t->extracted = (next3 == DHCP_CLIENT_DETECT_NEXT_EXTRACT); + } + + /* verify speculative enqueue, maybe switch current next frame */ + vlib_validate_buffer_enqueue_x4 (vm, node, next_index, + to_next, n_left_to_next, + bi0, bi1, bi2, bi3, + next0, next1, next2, next3); + } + + while (n_left_from > 0 && n_left_to_next > 0) + { + udp_header_t *udp0; + vlib_buffer_t *b0; + ip4_header_t *ip0; + u32 next0 = ~0; + u32 bi0; + + bi0 = from[0]; + to_next[0] = bi0; + from += 1; + to_next += 1; + n_left_from -= 1; + n_left_to_next -= 1; + + b0 = vlib_get_buffer (vm, bi0); + ip0 = vlib_buffer_get_current (b0); + + /* + * when this feature is applied on an interface that is already + * accepting packets (because e.g. the interface has other addresses + * assigned) we are looking for the preverbial needle in the haystack + * so assume the packet is not the one we are looking for. + */ + vnet_feature_next (vnet_buffer (b0)->sw_if_index[VLIB_TX], + &next0, b0); + + /* + * all we are looking for here is DHCP/BOOTP packet-to-client + * UDO port. + */ + if (ip0->protocol == IP_PROTOCOL_UDP) + { + udp0 = (udp_header_t *) (ip0 + 1); + + if (dhcp_client_port_network_order == udp0->dst_port) + { + next0 = DHCP_CLIENT_DETECT_NEXT_EXTRACT; + extractions++; + } + } + + if (PREDICT_FALSE (b0->flags & VLIB_BUFFER_IS_TRACED)) + { + dhcp_client_detect_trace_t *t = + vlib_add_trace (vm, node, b0, sizeof (*t)); + t->extracted = (next0 == DHCP_CLIENT_DETECT_NEXT_EXTRACT); + } + + /* 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_node_increment_counter (vm, node->node_index, + DHCP_CLIENT_DETECT_ERROR_EXTRACT, extractions); + + return frame->n_vectors; +} + +/* packet trace format function */ +static u8 * +format_dhcp_client_detect_trace (u8 * s, va_list * args) +{ + CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *); + CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *); + dhcp_client_detect_trace_t *t = + va_arg (*args, dhcp_client_detect_trace_t *); + + s = format (s, "dhcp-client-detect: %s", (t->extracted ? "yes" : "no")); + + return s; +} + +/* *INDENT-OFF* */ +VLIB_REGISTER_NODE (dhcp_client_detect_node) = { + .function = dhcp_client_detect_node_fn, + .name = "ip4-dhcp-client-detect", + .vector_size = sizeof (u32), + .format_trace = format_dhcp_client_detect_trace, + .type = VLIB_NODE_TYPE_INTERNAL, + + .n_errors = ARRAY_LEN(dhcp_client_detect_error_strings), + .error_strings = dhcp_client_detect_error_strings, + + .n_next_nodes = DHCP_CLIENT_DETECT_N_NEXT, + .next_nodes = { + /* + * Jump straight to the UDP dispatch node thus avoiding + * the RPF checks in ip4-local that will fail + */ + [DHCP_CLIENT_DETECT_NEXT_EXTRACT] = "ip4-udp-lookup", + }, +}; + +VLIB_NODE_FUNCTION_MULTIARCH (dhcp_client_detect_node, + dhcp_client_detect_node_fn); + +VNET_FEATURE_INIT (ip4_dvr_reinject_feat_node, static) = +{ + .arc_name = "ip4-unicast", + .node_name = "ip4-dhcp-client-detect", + .runs_before = VNET_FEATURES ("ip4-drop"), +}; + +/* *INDENT-ON* */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/vpp-api/vom/dhcp_config.cpp b/src/vpp-api/vom/dhcp_config.cpp index 0b6e2eff0de..8071fb15c57 100644 --- a/src/vpp-api/vom/dhcp_config.cpp +++ b/src/vpp-api/vom/dhcp_config.cpp @@ -24,20 +24,25 @@ singular_db dhcp_config::m_db; dhcp_config::event_handler dhcp_config::m_evh; -dhcp_config::dhcp_config(const interface& itf, const std::string& hostname) +dhcp_config::dhcp_config(const interface& itf, + const std::string& hostname, + bool set_broadcast_flag) : m_itf(itf.singular()) , m_hostname(hostname) , m_client_id(l2_address_t::ZERO) + , m_set_broadcast_flag(set_broadcast_flag) , m_binding(0) { } dhcp_config::dhcp_config(const interface& itf, const std::string& hostname, - const l2_address_t& client_id) + const l2_address_t& client_id, + bool set_broadcast_flag) : m_itf(itf.singular()) , m_hostname(hostname) , m_client_id(client_id) + , m_set_broadcast_flag(set_broadcast_flag) , m_binding(0) { } @@ -46,6 +51,7 @@ dhcp_config::dhcp_config(const dhcp_config& o) : m_itf(o.m_itf) , m_hostname(o.m_hostname) , m_client_id(o.m_client_id) + , m_set_broadcast_flag(o.m_set_broadcast_flag) , m_binding(0) { } diff --git a/src/vpp-api/vom/dhcp_config.hpp b/src/vpp-api/vom/dhcp_config.hpp index db97af98c19..8ea608d809d 100644 --- a/src/vpp-api/vom/dhcp_config.hpp +++ b/src/vpp-api/vom/dhcp_config.hpp @@ -41,14 +41,17 @@ public: /** * Construct a new object matching the desried state */ - dhcp_config(const interface& itf, const std::string& hostname); + dhcp_config(const interface& itf, + const std::string& hostname, + bool set_broadcast_flag = true); /** * Construct a new object matching the desried state */ dhcp_config(const interface& itf, const std::string& hostname, - const l2_address_t& client_id); + const l2_address_t& client_id, + bool set_broadcast_flag = true); /** * Copy Constructor @@ -202,6 +205,11 @@ private: */ const l2_address_t m_client_id; + /** + * Flag to control the setting the of DHCP discover's broadcast flag + */ + const bool m_set_broadcast_flag; + /** * HW configuration for the binding. The bool representing the * do/don't bind. diff --git a/src/vpp-api/vom/dhcp_config_cmds.cpp b/src/vpp-api/vom/dhcp_config_cmds.cpp index ff24fe2f463..9e803be7b8d 100644 --- a/src/vpp-api/vom/dhcp_config_cmds.cpp +++ b/src/vpp-api/vom/dhcp_config_cmds.cpp @@ -23,11 +23,13 @@ namespace dhcp_config_cmds { bind_cmd::bind_cmd(HW::item& item, const handle_t& itf, const std::string& hostname, - const l2_address_t& client_id) + const l2_address_t& client_id, + bool set_broadcast_flag) : rpc_cmd(item) , m_itf(itf) , m_hostname(hostname) , m_client_id(client_id) + , m_set_broadcast_flag(set_broadcast_flag) { } diff --git a/src/vpp-api/vom/dhcp_config_cmds.hpp b/src/vpp-api/vom/dhcp_config_cmds.hpp index 863cf599b74..726ff992577 100644 --- a/src/vpp-api/vom/dhcp_config_cmds.hpp +++ b/src/vpp-api/vom/dhcp_config_cmds.hpp @@ -37,7 +37,8 @@ public: bind_cmd(HW::item& item, const handle_t& itf, const std::string& hostname, - const l2_address_t& client_id); + const l2_address_t& client_id, + bool set_braodcast_flag = false); /** * Issue the command to VPP/HW @@ -68,6 +69,11 @@ private: * The DHCP client's ID */ const l2_address_t m_client_id; + + /** + * Flag to control the setting the of DHCP discover's broadcast flag + */ + const bool m_set_broadcast_flag; }; /** diff --git a/test/test_dhcp.py b/test/test_dhcp.py index db7a7cc223c..21940ca99f0 100644 --- a/test/test_dhcp.py +++ b/test/test_dhcp.py @@ -214,7 +214,8 @@ class TestDHCP(VppTestCase): self.assertEqual(udp.dport, DHCP4_SERVER_PORT) self.assertEqual(udp.sport, DHCP4_CLIENT_PORT) - def verify_orig_dhcp_discover(self, pkt, intf, hostname, client_id=None): + def verify_orig_dhcp_discover(self, pkt, intf, hostname, client_id=None, + broadcast=1): self.verify_orig_dhcp_pkt(pkt, intf) self.verify_dhcp_msg_type(pkt, "discover") @@ -224,9 +225,13 @@ class TestDHCP(VppTestCase): bootp = pkt[BOOTP] self.assertEqual(bootp.ciaddr, "0.0.0.0") self.assertEqual(bootp.giaddr, "0.0.0.0") - self.assertEqual(bootp.flags, 0x8000) + if broadcast: + self.assertEqual(bootp.flags, 0x8000) + else: + self.assertEqual(bootp.flags, 0x0000) - def verify_orig_dhcp_request(self, pkt, intf, hostname, ip): + def verify_orig_dhcp_request(self, pkt, intf, hostname, ip, + broadcast=1): self.verify_orig_dhcp_pkt(pkt, intf) self.verify_dhcp_msg_type(pkt, "request") @@ -235,7 +240,10 @@ class TestDHCP(VppTestCase): bootp = pkt[BOOTP] self.assertEqual(bootp.ciaddr, "0.0.0.0") self.assertEqual(bootp.giaddr, "0.0.0.0") - self.assertEqual(bootp.flags, 0x8000) + if broadcast: + self.assertEqual(bootp.flags, 0x8000) + else: + self.assertEqual(bootp.flags, 0x0000) def verify_relayed_dhcp_discover(self, pkt, intf, src_intf=None, fib_id=0, oui=0, @@ -1310,6 +1318,14 @@ class TestDHCP(VppTestCase): self.pg_enable_capture(self.pg_interfaces) self.pg_start() + # + # We'll get an ARP request for the router address + # + rx = self.pg3.get_capture(1) + + self.assertEqual(rx[0][ARP].pdst, self.pg3.remote_ip4) + self.pg_enable_capture(self.pg_interfaces) + # # At the end of this procedure there should be a connected route # in the FIB @@ -1325,6 +1341,91 @@ class TestDHCP(VppTestCase): self.assertFalse(find_route(self, self.pg3.local_ip4, 32)) self.assertFalse(find_route(self, self.pg3.local_ip4, 24)) + # + # Rince and repeat, this time with VPP configured not to set + # the braodcast flag in the discover and request messages, + # and for the server to unicast the responses. + # + # Configure DHCP client on PG3 and capture the discover sent + # + self.vapi.dhcp_client(self.pg3.sw_if_index, hostname, + set_broadcast_flag=0) + + rx = self.pg3.get_capture(1) + + self.verify_orig_dhcp_discover(rx[0], self.pg3, hostname, + broadcast=0) + + # + # Send back on offer, unicasted to the offered address. + # Expect the request. + # + p_offer = (Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac) / + IP(src=self.pg3.remote_ip4, dst=self.pg3.local_ip4) / + UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT) / + BOOTP(op=1, yiaddr=self.pg3.local_ip4) / + DHCP(options=[('message-type', 'offer'), + ('server_id', self.pg3.remote_ip4), + ('end')])) + + self.pg3.add_stream(p_offer) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + rx = self.pg3.get_capture(1) + self.verify_orig_dhcp_request(rx[0], self.pg3, hostname, + self.pg3.local_ip4, + broadcast=0) + + # + # Send an acknowloedgement + # + p_ack = (Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac) / + IP(src=self.pg3.remote_ip4, dst=self.pg3.local_ip4) / + UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT) / + BOOTP(op=1, yiaddr=self.pg3.local_ip4) / + DHCP(options=[('message-type', 'ack'), + ('subnet_mask', "255.255.255.0"), + ('router', self.pg3.remote_ip4), + ('server_id', self.pg3.remote_ip4), + ('lease_time', 43200), + ('end')])) + + self.pg3.add_stream(p_ack) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + # + # We'll get an ARP request for the router address + # + rx = self.pg3.get_capture(1) + + self.assertEqual(rx[0][ARP].pdst, self.pg3.remote_ip4) + self.pg_enable_capture(self.pg_interfaces) + + # + # At the end of this procedure there should be a connected route + # in the FIB + # + self.assertTrue(find_route(self, self.pg3.local_ip4, 24)) + self.assertTrue(find_route(self, self.pg3.local_ip4, 32)) + + # remove the left over ARP entry + self.vapi.ip_neighbor_add_del(self.pg3.sw_if_index, + mactobinary(self.pg3.remote_mac), + self.pg3.remote_ip4, + is_add=0) + # + # remove the DHCP config + # + self.vapi.dhcp_client(self.pg3.sw_if_index, hostname, is_add=0) + + # + # and now the route should be gone + # + self.assertFalse(find_route(self, self.pg3.local_ip4, 32)) + self.assertFalse(find_route(self, self.pg3.local_ip4, 24)) + if __name__ == '__main__': unittest.main(testRunner=VppTestRunner) diff --git a/test/vpp_papi_provider.py b/test/vpp_papi_provider.py index 13dccc9283b..db0f8e61aed 100644 --- a/test/vpp_papi_provider.py +++ b/test/vpp_papi_provider.py @@ -2132,6 +2132,7 @@ class VppPapiProvider(object): hostname, client_id='', is_add=1, + set_broadcast_flag=1, want_dhcp_events=0): return self.api( self.papi.dhcp_client_config, @@ -2141,6 +2142,7 @@ class VppPapiProvider(object): 'client_id': client_id, 'is_add': is_add, 'want_dhcp_event': want_dhcp_events, + 'set_broadcast_flag': set_broadcast_flag, 'pid': os.getpid(), }) -- 2.16.6