summaryrefslogtreecommitdiffstats
path: root/src/vnet/dhcp/dhcp_client_detect.c
blob: c79970d1456bc7d4a59a5dc35a3ef79e64d11156 (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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
/*
 * 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 <vnet/dhcp/client.h>
#include <vnet/udp/udp.h>

#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;

VLIB_NODE_FN (dhcp_client_detect_node) (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 receiving '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 (&next0, b0);
	  vnet_feature_next (&next1, b1);
	  vnet_feature_next (&next2, b2);
	  vnet_feature_next (&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 (&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) = {
  .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",
  },
};

VNET_FEATURE_INIT (ip4_dvr_reinject_feat_node, static) =
{
  .arc_name = "ip4-unicast",
  .node_name = "ip4-dhcp-client-detect",
  .runs_before = VNET_FEATURES ("ip4-not-enabled"),
};

/* *INDENT-ON* */

/*
 * fd.io coding-style-patch-verification: ON
 *
 * Local Variables:
 * eval: (c-set-style "gnu")
 * End:
 */
lass="kt">void dhcp_client_proc_callback (uword * client_index) { vlib_main_t *vm = vlib_get_main (); ASSERT (vlib_get_thread_index () == 0); vlib_process_signal_event (vm, dhcp_client_process_node.index, EVENT_DHCP_CLIENT_WAKEUP, *client_index); } static void dhcp_client_addr_callback (dhcp_client_t * c) { dhcp_client_main_t *dcm = &dhcp_client_main; /* disable the feature */ vnet_feature_enable_disable ("ip4-unicast", "ip4-dhcp-client-detect", c->sw_if_index, 0 /* disable */ , 0, 0); c->client_detect_feature_enabled = 0; /* if renewing the lease, the address and route have already been added */ if (c->state == DHCP_BOUND) return; /* add the address to the interface */ dhcp_client_acquire_address (dcm, c); /* * Configure default IP route: */ if (c->router_address.as_u32) { fib_prefix_t all_0s = { .fp_len = 0, .fp_addr.ip4.as_u32 = 0x0, .fp_proto = FIB_PROTOCOL_IP4, }; ip46_address_t nh = { .ip4 = c->router_address, }; /* *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* */ } /* * Call the user's event callback to report DHCP information */ if (c->event_callback) c->event_callback (c->client_index, c); } /* * dhcp_client_for_us - server-to-client callback. * Called from proxy_node.c:dhcp_proxy_to_client_input(). * This function first decides that the packet in question is * actually for the dhcp client code in case we're also acting as * a dhcp proxy. Ay caramba, what a folly! */ int dhcp_client_for_us (u32 bi, vlib_buffer_t * b, ip4_header_t * ip, udp_header_t * udp, dhcp_header_t * dhcp) { dhcp_client_main_t *dcm = &dhcp_client_main; vlib_main_t *vm = dcm->vlib_main; dhcp_client_t *c; uword *p; f64 now = vlib_time_now (dcm->vlib_main); u8 dhcp_message_type = 0; dhcp_option_t *o; /* * Doing dhcp client on this interface? * Presumably we will always receive dhcp clnt for-us pkts on * the interface that's asking for an address. */ p = hash_get (dcm->client_by_sw_if_index, vnet_buffer (b)->sw_if_index[VLIB_RX]); if (p == 0) return 0; /* no */ c = pool_elt_at_index (dcm->clients, p[0]); /* Mixing dhcp relay and dhcp proxy? DGMS... */ if (c->state == DHCP_BOUND && c->retry_count == 0) return 0; /* Packet not for us? Turf it... */ if (memcmp (dhcp->client_hardware_address, c->client_hardware_address, sizeof (c->client_hardware_address))) { vlib_node_increment_counter (vm, dhcp_client_process_node.index, DHCP_STAT_NOT_FOR_US, 1); return 0; } /* parse through the packet, learn what we can */ if (dhcp->your_ip_address.as_u32) c->leased_address.as_u32 = dhcp->your_ip_address.as_u32; c->dhcp_server.as_u32 = dhcp->server_ip_address.as_u32; o = (dhcp_option_t *) dhcp->options; while (o->option != 0xFF /* end of options */ && (u8 *) o < (b->data + b->current_data + b->current_length)) { switch (o->option) { case 53: /* dhcp message type */ dhcp_message_type = o->data[0]; break; case 51: /* lease time */ { u32 lease_time_in_seconds = clib_host_to_net_u32 (o->data_as_u32[0]); // for debug: lease_time_in_seconds = 20; /*$$$$*/ c->lease_expires = now + (f64) lease_time_in_seconds; c->lease_lifetime = lease_time_in_seconds; /* Set a sensible default, in case we don't get opt 58 */ c->lease_renewal_interval = lease_time_in_seconds / 2; } break; case 58: /* lease renew time in seconds */ { u32 lease_renew_time_in_seconds = clib_host_to_net_u32 (o->data_as_u32[0]); c->lease_renewal_interval = lease_renew_time_in_seconds; } break; case 54: /* dhcp server address */ c->dhcp_server.as_u32 = o->data_as_u32[0]; break; case 1: /* subnet mask */ { u32 subnet_mask = clib_host_to_net_u32 (o->data_as_u32[0]); c->subnet_mask_width = count_set_bits (subnet_mask); } break; case 3: /* router address */ { u32 router_address = o->data_as_u32[0]; c->router_address.as_u32 = router_address; } break; case 6: /* domain server address */ { vec_free (c->domain_server_address); vec_validate (c->domain_server_address, o->length / sizeof (ip4_address_t) - 1); clib_memcpy (c->domain_server_address, o->data, o->length); } break; case 12: /* hostname */ { /* Replace the existing hostname if necessary */ vec_free (c->hostname); vec_validate (c->hostname, o->length - 1); clib_memcpy (c->hostname, o->data, o->length); } break; /* $$$$ Your message in this space, parse more options */ default: break; } o = (dhcp_option_t *) (((uword) o) + (o->length + 2)); } switch (c->state) { case DHCP_DISCOVER: if (dhcp_message_type != DHCP_PACKET_OFFER) { vlib_node_increment_counter (vm, dhcp_client_process_node.index, DHCP_STAT_NON_OFFER_DISCOVER, 1); c->next_transmit = now + 5.0; break; } /* Received an offer, go send a request */ c->state = DHCP_REQUEST; c->retry_count = 0; c->next_transmit = 0; /* send right now... */ /* Poke the client process, which will send the request */ uword client_id = c - dcm->clients; vl_api_rpc_call_main_thread (dhcp_client_proc_callback, (u8 *) & client_id, sizeof (uword)); break; case DHCP_BOUND: case DHCP_REQUEST: if (dhcp_message_type == DHCP_PACKET_NAK) { vlib_node_increment_counter (vm, dhcp_client_process_node.index, DHCP_STAT_NAK, 1); /* Probably never happens in bound state, but anyhow... */ if (c->state == DHCP_BOUND) { ip4_add_del_interface_address (dcm->vlib_main, c->sw_if_index, (void *) &c->leased_address, c->subnet_mask_width, 1 /*is_del */ ); vnet_feature_enable_disable ("ip4-unicast", "ip4-dhcp-client-detect", c->sw_if_index, 1 /* enable */ , 0, 0); c->client_detect_feature_enabled = 1; } /* Wipe out any memory of the address we had... */ c->state = DHCP_DISCOVER; c->next_transmit = now; c->retry_count = 0; c->leased_address.as_u32 = 0; c->subnet_mask_width = 0; c->router_address.as_u32 = 0; c->lease_renewal_interval = 0; c->dhcp_server.as_u32 = 0; vec_free (c->domain_server_address); break; } if (dhcp_message_type != DHCP_PACKET_ACK && dhcp_message_type != DHCP_PACKET_OFFER) { vlib_node_increment_counter (vm, dhcp_client_process_node.index, DHCP_STAT_NON_OFFER_DISCOVER, 1); clib_warning ("sw_if_index %d state %U message type %d", c->sw_if_index, format_dhcp_client_state, c->state, dhcp_message_type); c->next_transmit = now + 5.0; break; } /* OK, we own the address (etc), add to the routing table(s) */ vl_api_rpc_call_main_thread (dhcp_client_addr_callback, (u8 *) c, sizeof (*c)); c->state = DHCP_BOUND; c->retry_count = 0; c->next_transmit = now + (f64) c->lease_renewal_interval; c->lease_expires = now + (f64) c->lease_lifetime; vlib_node_increment_counter (vm, dhcp_client_process_node.index, DHCP_STAT_BOUND, 1); break; default: clib_warning ("client %d bogus state %d", c - dcm->clients, c->state); break; } /* drop the pkt, return 1 */ vlib_buffer_free (vm, &bi, 1); return 1; } static void send_dhcp_pkt (dhcp_client_main_t * dcm, dhcp_client_t * c, dhcp_packet_type_t type, int is_broadcast) { vlib_main_t *vm = dcm->vlib_main; vnet_main_t *vnm = dcm->vnet_main; vnet_hw_interface_t *hw = vnet_get_sup_hw_interface (vnm, c->sw_if_index); vnet_sw_interface_t *sup_sw = vnet_get_sup_sw_interface (vnm, c->sw_if_index); vnet_sw_interface_t *sw = vnet_get_sw_interface (vnm, c->sw_if_index); vlib_buffer_t *b; u32 bi; ip4_header_t *ip; udp_header_t *udp; dhcp_header_t *dhcp; u32 *to_next; vlib_frame_t *f; dhcp_option_t *o; u16 udp_length, ip_length; u32 counter_index; /* Interface(s) down? */ if ((hw->flags & VNET_HW_INTERFACE_FLAG_LINK_UP) == 0) return; if ((sup_sw->flags & VNET_SW_INTERFACE_FLAG_ADMIN_UP) == 0) return; if ((sw->flags & VNET_SW_INTERFACE_FLAG_ADMIN_UP) == 0) return; if (vlib_buffer_alloc (vm, &bi, 1) != 1) { clib_warning ("buffer allocation failure"); c->next_transmit = 0; return; } /* Build a dhcpv4 pkt from whole cloth */ b = vlib_get_buffer (vm, bi); ASSERT (b->current_data == 0); vnet_buffer (b)->sw_if_index[VLIB_RX] = c->sw_if_index; if (is_broadcast) { f = vlib_get_frame_to_node (vm, hw->output_node_index); vnet_buffer (b)->sw_if_index[VLIB_TX] = c->sw_if_index; clib_memcpy (b->data, c->l2_rewrite, vec_len (c->l2_rewrite)); ip = (void *) (((u8 *) vlib_buffer_get_current (b)) + vec_len (c->l2_rewrite)); } else { f = vlib_get_frame_to_node (vm, ip4_lookup_node.index); vnet_buffer (b)->sw_if_index[VLIB_TX] = ~0; /* use interface VRF */ ip = vlib_buffer_get_current (b); } /* Enqueue the packet right now */ to_next = vlib_frame_vector_args (f); to_next[0] = bi; f->n_vectors = 1; if (is_broadcast) vlib_put_frame_to_node (vm, hw->output_node_index, f); else vlib_put_frame_to_node (vm, ip4_lookup_node.index, f); udp = (udp_header_t *) (ip + 1); dhcp = (dhcp_header_t *) (udp + 1); /* $$$ optimize, maybe */ clib_memset (ip, 0, sizeof (*ip) + sizeof (*udp) + sizeof (*dhcp)); ip->ip_version_and_header_length = 0x45; ip->ttl = 128; ip->protocol = IP_PROTOCOL_UDP; if (is_broadcast) { /* src = 0.0.0.0, dst = 255.255.255.255 */ ip->dst_address.as_u32 = ~0; } else { /* Renewing an active lease, plain old ip4 src/dst */ ip->src_address.as_u32 = c->leased_address.as_u32; ip->dst_address.as_u32 = c->dhcp_server.as_u32; } udp->src_port = clib_host_to_net_u16 (UDP_DST_PORT_dhcp_to_client); udp->dst_port = clib_host_to_net_u16 (UDP_DST_PORT_dhcp_to_server); /* Send the interface MAC address */ clib_memcpy (dhcp->client_hardware_address, c->l2_rewrite + 6, 6); /* And remember it for rx-packet-for-us checking */ clib_memcpy (c->client_hardware_address, dhcp->client_hardware_address, sizeof (c->client_hardware_address)); /* Lease renewal, set up client_ip_address */ if (is_broadcast == 0) dhcp->client_ip_address.as_u32 = c->leased_address.as_u32; dhcp->opcode = 1; /* request, all we send */ 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 && c->set_broadcast_flag ? DHCP_FLAG_BROADCAST : 0); dhcp->magic_cookie.as_u32 = DHCP_MAGIC; o = (dhcp_option_t *) dhcp->options; /* Send option 53, the DHCP message type */ o->option = DHCP_PACKET_OPTION_MSG_TYPE; o->length = 1; o->data[0] = type; o = (dhcp_option_t *) (((uword) o) + (o->length + 2)); /* Send option 57, max msg length */ if (0 /* not needed, apparently */ ) { o->option = 57; o->length = 2; { u16 *o2 = (u16 *) o->data; *o2 = clib_host_to_net_u16 (1152); o = (dhcp_option_t *) (((uword) o) + (o->length + 2)); } } /* * If server ip address is available with non-zero value, * option 54 (DHCP Server Identifier) is sent. */ if (c->dhcp_server.as_u32) { o->option = 54; o->length = 4; clib_memcpy (o->data, &c->dhcp_server.as_u32, 4); o = (dhcp_option_t *) (((uword) o) + (o->length + 2)); } /* send option 50, requested IP address */ if (c->leased_address.as_u32) { o->option = 50; o->length = 4; clib_memcpy (o->data, &c->leased_address.as_u32, 4); o = (dhcp_option_t *) (((uword) o) + (o->length + 2)); } /* send option 12, host name */ if (vec_len (c->hostname)) { o->option = 12; o->length = vec_len (c->hostname); clib_memcpy (o->data, c->hostname, vec_len (c->hostname)); o = (dhcp_option_t *) (((uword) o) + (o->length + 2)); } /* send option 61, client_id */ if (vec_len (c->client_identifier)) { o->option = 61; o->length = vec_len (c->client_identifier); clib_memcpy (o->data, c->client_identifier, vec_len (c->client_identifier)); o = (dhcp_option_t *) (((uword) o) + (o->length + 2)); } /* $$ maybe send the client s/w version if anyone cares */ /* * send option 55, parameter request list * The current list - see below, matches the Linux dhcp client's list * Any specific dhcp server config and/or dhcp server may or may * not yield specific options. */ o->option = 55; o->length = vec_len (c->option_55_data); clib_memcpy (o->data, c->option_55_data, vec_len (c->option_55_data)); o = (dhcp_option_t *) (((uword) o) + (o->length + 2)); /* End of list */ o->option = 0xff; o->length = 0; o++; b->current_length = ((u8 *) o) - b->data; /* fix ip length, checksum and udp length */ ip_length = vlib_buffer_length_in_chain (vm, b); if (is_broadcast) ip_length -= vec_len (c->l2_rewrite); ip->length = clib_host_to_net_u16 (ip_length); ip->checksum = ip4_header_checksum (ip); udp_length = ip_length - (sizeof (*ip)); udp->length = clib_host_to_net_u16 (udp_length); switch (type) { #define _(a,b) case DHCP_PACKET_##a: {counter_index = DHCP_STAT_##a; break;} foreach_dhcp_sent_packet_stat #undef _ default: counter_index = DHCP_STAT_UNKNOWN; break; } vlib_node_increment_counter (vm, dhcp_client_process_node.index, counter_index, 1); } static int dhcp_discover_state (dhcp_client_main_t * dcm, dhcp_client_t * c, f64 now) { /* * State machine "DISCOVER" state. Send a dhcp discover packet, * eventually back off the retry rate. */ if (c->client_detect_feature_enabled == 0) { vnet_feature_enable_disable ("ip4-unicast", "ip4-dhcp-client-detect", c->sw_if_index, 1 /* enable */ , 0, 0); c->client_detect_feature_enabled = 1; } send_dhcp_pkt (dcm, c, DHCP_PACKET_DISCOVER, 1 /* is_broadcast */ ); c->retry_count++; if (c->retry_count > 10) c->next_transmit = now + 5.0; else c->next_transmit = now + 1.0; return 0; } static int dhcp_request_state (dhcp_client_main_t * dcm, dhcp_client_t * c, f64 now) { /* * State machine "REQUEST" state. Send a dhcp request packet, * eventually drop back to the discover state. */ send_dhcp_pkt (dcm, c, DHCP_PACKET_REQUEST, 1 /* is_broadcast */ ); c->retry_count++; if (c->retry_count > 7 /* lucky you */ ) { c->state = DHCP_DISCOVER; c->next_transmit = now; c->retry_count = 0; return 1; } c->next_transmit = now + 1.0; return 0; } static int dhcp_bound_state (dhcp_client_main_t * dcm, dhcp_client_t * c, f64 now) { /* * State machine "BOUND" state. Send a dhcp request packet to renew * the lease. * Eventually, when the lease expires, forget the dhcp data * and go back to the stone age. */ /* * We disable the client detect feature when we bind a * DHCP address. Turn it back on again on first renew attempt. * Otherwise, if the DHCP server replies we'll never see it. */ if (c->client_detect_feature_enabled == 0) { vnet_feature_enable_disable ("ip4-unicast", "ip4-dhcp-client-detect", c->sw_if_index, 1 /* enable */ , 0, 0); c->client_detect_feature_enabled = 1; } send_dhcp_pkt (dcm, c, DHCP_PACKET_REQUEST, 0 /* is_broadcast */ ); c->retry_count++; if (c->retry_count > 10) c->next_transmit = now + 5.0; else c->next_transmit = now + 1.0; if (now > c->lease_expires) { /* Remove the default route */ if (c->router_address.as_u32) { fib_prefix_t all_0s = { .fp_len = 0, .fp_addr.ip4.as_u32 = 0x0, .fp_proto = FIB_PROTOCOL_IP4, }; ip46_address_t nh = { .ip4 = c->router_address, }; fib_table_entry_path_remove (fib_table_get_index_for_sw_if_index (FIB_PROTOCOL_IP4, c->sw_if_index), &all_0s, FIB_SOURCE_DHCP, DPO_PROTO_IP4, &nh, c->sw_if_index, ~0, 1, FIB_ROUTE_PATH_FLAG_NONE); } /* Remove the interface address */ dhcp_client_release_address (dcm, c); c->state = DHCP_DISCOVER; c->next_transmit = now; c->retry_count = 0; /* Wipe out any memory of the address we had... */ c->leased_address.as_u32 = 0; c->subnet_mask_width = 0; c->router_address.as_u32 = 0; c->lease_renewal_interval = 0; c->dhcp_server.as_u32 = 0; return 1; } return 0; } static f64 dhcp_client_sm (f64 now, f64 timeout, uword pool_index) { dhcp_client_main_t *dcm = &dhcp_client_main; dhcp_client_t *c; /* deleted, pooched, yadda yadda yadda */ if (pool_is_free_index (dcm->clients, pool_index)) return timeout; c = pool_elt_at_index (dcm->clients, pool_index); /* Time for us to do something with this client? */ if (now < c->next_transmit) return timeout; again: switch (c->state) { case DHCP_DISCOVER: /* send a discover */ if (dhcp_discover_state (dcm, c, now)) goto again; break; case DHCP_REQUEST: /* send a request */ if (dhcp_request_state (dcm, c, now)) goto again; break; case DHCP_BOUND: /* bound, renew needed? */ if (dhcp_bound_state (dcm, c, now)) goto again; break; default: clib_warning ("dhcp client %d bogus state %d", c - dcm->clients, c->state); break; } if (c->next_transmit < now + timeout) return c->next_transmit - now; return timeout; } static uword dhcp_client_process (vlib_main_t * vm, vlib_node_runtime_t * rt, vlib_frame_t * f) { f64 timeout = 100.0; f64 now; uword event_type; uword *event_data = 0; dhcp_client_main_t *dcm = &dhcp_client_main; dhcp_client_t *c; int i; while (1) { vlib_process_wait_for_event_or_clock (vm, timeout); event_type = vlib_process_get_events (vm, &event_data); now = vlib_time_now (vm); switch (event_type) { case EVENT_DHCP_CLIENT_WAKEUP: for (i = 0; i < vec_len (event_data); i++) timeout = dhcp_client_sm (now, timeout, event_data[i]); break; case ~0: /* *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; } vec_reset_length (event_data); } /* NOTREACHED */ return 0; } /* *INDENT-OFF* */ VLIB_REGISTER_NODE (dhcp_client_process_node,static) = { .function = dhcp_client_process, .type = VLIB_NODE_TYPE_PROCESS, .name = "dhcp-client-process", .process_log2_n_stack_bytes = 16, .n_errors = ARRAY_LEN(dhcp_client_process_stat_strings), .error_strings = dhcp_client_process_stat_strings, }; /* *INDENT-ON* */ static u8 * format_dhcp_client_state (u8 * s, va_list * va) { dhcp_client_state_t state = va_arg (*va, dhcp_client_state_t); char *str = "BOGUS!"; switch (state) { #define _(a) \ case a: \ str = #a; \ break; foreach_dhcp_client_state; #undef _ default: break; } s = format (s, "%s", str); return s; } static u8 * format_dhcp_client (u8 * s, va_list * va) { dhcp_client_main_t *dcm = va_arg (*va, dhcp_client_main_t *); dhcp_client_t *c = va_arg (*va, dhcp_client_t *); int verbose = va_arg (*va, int); ip4_address_t *addr; s = format (s, "[%d] %U state %U ", c - dcm->clients, format_vnet_sw_if_index_name, dcm->vnet_main, c->sw_if_index, format_dhcp_client_state, c->state); if (c->leased_address.as_u32) { s = format (s, "addr %U/%d gw %U", format_ip4_address, &c->leased_address, c->subnet_mask_width, format_ip4_address, &c->router_address); vec_foreach (addr, c->domain_server_address) s = format (s, " dns %U", format_ip4_address, addr); vec_add1 (s, '\n'); } else { s = format (s, "no address\n"); } if (verbose) { s = format (s, "retry count %d, next xmt %.2f", c->retry_count, c->next_transmit); } return s; } static clib_error_t * show_dhcp_client_command_fn (vlib_main_t * vm, unformat_input_t * input, vlib_cli_command_t * cmd) { dhcp_client_main_t *dcm = &dhcp_client_main; dhcp_client_t *c; int verbose = 0; u32 sw_if_index = ~0; uword *p; while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) { if (unformat (input, "intfc %U", unformat_vnet_sw_interface, dcm->vnet_main, &sw_if_index)) ; else if (unformat (input, "verbose")) verbose = 1; else break; } if (sw_if_index != ~0) { p = hash_get (dcm->client_by_sw_if_index, sw_if_index); if (p == 0) return clib_error_return (0, "dhcp client not configured"); c = pool_elt_at_index (dcm->clients, p[0]); vlib_cli_output (vm, "%U", format_dhcp_client, dcm, c, verbose); return 0; } /* *INDENT-OFF* */ pool_foreach (c, dcm->clients, ({ vlib_cli_output (vm, "%U", format_dhcp_client, dcm, c, verbose); })); /* *INDENT-ON* */ return 0; } /* *INDENT-OFF* */ VLIB_CLI_COMMAND (show_dhcp_client_command, static) = { .path = "show dhcp client", .short_help = "show dhcp client [intfc <intfc>][verbose]", .function = show_dhcp_client_command_fn, }; /* *INDENT-ON* */ int dhcp_client_add_del (dhcp_client_add_del_args_t * a) { dhcp_client_main_t *dcm = &dhcp_client_main; vlib_main_t *vm = dcm->vlib_main; dhcp_client_t *c; uword *p; fib_prefix_t all_0s = { .fp_len = 0, .fp_addr.ip4.as_u32 = 0x0, .fp_proto = FIB_PROTOCOL_IP4, }; p = hash_get (dcm->client_by_sw_if_index, a->sw_if_index); if ((p && a->is_add) || (!p && a->is_add == 0)) return VNET_API_ERROR_INVALID_VALUE; if (a->is_add) { dhcp_maybe_register_udp_ports (DHCP_PORT_REG_CLIENT); pool_get (dcm->clients, c); clib_memset (c, 0, sizeof (*c)); c->state = DHCP_DISCOVER; c->sw_if_index = a->sw_if_index; c->client_index = a->client_index; c->pid = a->pid; c->event_callback = a->event_callback; 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); } while (c->transaction_id == 0); set_l2_rewrite (dcm, c); hash_set (dcm->client_by_sw_if_index, a->sw_if_index, c - dcm->clients); /* * 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 */ vnet_feature_enable_disable ("ip4-unicast", "ip4-dhcp-client-detect", c->sw_if_index, 1 /* enable */ , 0, 0); c->client_detect_feature_enabled = 1; vlib_process_signal_event (vm, dhcp_client_process_node.index, EVENT_DHCP_CLIENT_WAKEUP, c - dcm->clients); } else { c = pool_elt_at_index (dcm->clients, p[0]); if (c->router_address.as_u32) { ip46_address_t nh = { .ip4 = c->router_address, }; fib_table_entry_path_remove (fib_table_get_index_for_sw_if_index (FIB_PROTOCOL_IP4, c->sw_if_index), &all_0s, FIB_SOURCE_DHCP, DPO_PROTO_IP4, &nh, c->sw_if_index, ~0, 1, FIB_ROUTE_PATH_FLAG_NONE); } dhcp_client_release_address (dcm, c); vec_free (c->domain_server_address); vec_free (c->option_55_data); vec_free (c->hostname); vec_free (c->client_identifier); vec_free (c->l2_rewrite); hash_unset (dcm->client_by_sw_if_index, c->sw_if_index); pool_put (dcm->clients, c); } return 0; } int dhcp_client_config (u32 is_add, u32 client_index, vlib_main_t * vm, u32 sw_if_index, u8 * hostname, u8 * client_id, dhcp_event_cb_t event_callback, u8 set_broadcast_flag, u32 pid) { dhcp_client_add_del_args_t _a, *a = &_a; int rv; clib_memset (a, 0, sizeof (*a)); a->is_add = is_add; a->sw_if_index = sw_if_index; 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); strncpy ((char *) a->client_identifier, (char *) client_id, vec_len (a->client_identifier)); /* * Option 55 request list. These data precisely match * the Ubuntu dhcp client. YMMV. */ /* Subnet Mask */ vec_add1 (a->option_55_data, 1); /* Broadcast address */ vec_add1 (a->option_55_data, 28); /* time offset */ vec_add1 (a->option_55_data, 2); /* Router */ vec_add1 (a->option_55_data, 3); /* Domain Name */ vec_add1 (a->option_55_data, 15); /* DNS */ vec_add1 (a->option_55_data, 6); /* Domain search */ vec_add1 (a->option_55_data, 119); /* Host name */ vec_add1 (a->option_55_data, 12); /* NetBIOS name server */ vec_add1 (a->option_55_data, 44); /* NetBIOS Scope */ vec_add1 (a->option_55_data, 47); /* MTU */ vec_add1 (a->option_55_data, 26); /* Classless static route */ vec_add1 (a->option_55_data, 121); /* NTP servers */ vec_add1 (a->option_55_data, 42); rv = dhcp_client_add_del (a); switch (rv) { case 0: break; case VNET_API_ERROR_INVALID_VALUE: vec_free (a->hostname); vec_free (a->client_identifier); vec_free (a->option_55_data); if (is_add) clib_warning ("dhcp client already enabled on intf_idx %d", sw_if_index); else clib_warning ("dhcp client not enabled on on intf_idx %d", sw_if_index); break; default: clib_warning ("dhcp_client_add_del returned %d", rv); } return rv; } void dhcp_client_walk (dhcp_client_walk_cb_t cb, void *ctx) { dhcp_client_main_t *dcm = &dhcp_client_main; dhcp_client_t *c; /* *INDENT-OFF* */ pool_foreach (c, dcm->clients, ({ if (!cb(c, ctx)) break; })); /* *INDENT-ON* */ } static clib_error_t * dhcp_client_set_command_fn (vlib_main_t * vm, unformat_input_t * input, vlib_cli_command_t * cmd) { dhcp_client_main_t *dcm = &dhcp_client_main; 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; while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) { if (unformat (input, "intfc %U", unformat_vnet_sw_interface, dcm->vnet_main, &sw_if_index)) sw_if_index_set = 1; else if (unformat (input, "hostname %v", &hostname)) ; else if (unformat (input, "del")) is_add = 0; else if (unformat (input, "broadcast", &set_broadcast_flag)) is_add = 0; else break; } if (sw_if_index_set == 0) return clib_error_return (0, "interface not specified"); clib_memset (a, 0, sizeof (*a)); a->is_add = is_add; 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 * the Ubuntu dhcp client. YMMV. */ /* Subnet Mask */ vec_add1 (a->option_55_data, 1); /* Broadcast address */ vec_add1 (a->option_55_data, 28); /* time offset */ vec_add1 (a->option_55_data, 2); /* Router */ vec_add1 (a->option_55_data, 3); /* Domain Name */ vec_add1 (a->option_55_data, 15); /* DNS */ vec_add1 (a->option_55_data, 6); /* Domain search */ vec_add1 (a->option_55_data, 119); /* Host name */ vec_add1 (a->option_55_data, 12); /* NetBIOS name server */ vec_add1 (a->option_55_data, 44); /* NetBIOS Scope */ vec_add1 (a->option_55_data, 47); /* MTU */ vec_add1 (a->option_55_data, 26); /* Classless static route */ vec_add1 (a->option_55_data, 121); /* NTP servers */ vec_add1 (a->option_55_data, 42); rv = dhcp_client_add_del (a); switch (rv) { case 0: break; case VNET_API_ERROR_INVALID_VALUE: vec_free (a->hostname); vec_free (a->client_identifier); vec_free (a->option_55_data); if (is_add) return clib_error_return (0, "dhcp client already enabled on %U", format_vnet_sw_if_index_name, dcm->vnet_main, sw_if_index); else return clib_error_return (0, "dhcp client not enabled on %U", format_vnet_sw_if_index_name, dcm->vnet_main, sw_if_index); break; default: vlib_cli_output (vm, "dhcp_client_add_del returned %d", rv); } return 0; } /* *INDENT-OFF* */ VLIB_CLI_COMMAND (dhcp_client_set_command, static) = { .path = "set dhcp client", .short_help = "set dhcp client [del] intfc <interface> [hostname <name>]", .function = dhcp_client_set_command_fn, }; /* *INDENT-ON* */ static clib_error_t * dhcp_client_init (vlib_main_t * vm) { dhcp_client_main_t *dcm = &dhcp_client_main; dcm->vlib_main = vm; dcm->vnet_main = vnet_get_main (); dcm->seed = (u32) clib_cpu_time_now (); return 0; } VLIB_INIT_FUNCTION (dhcp_client_init); /* * fd.io coding-style-patch-verification: ON * * Local Variables: * eval: (c-set-style "gnu") * End: */