aboutsummaryrefslogtreecommitdiffstats
path: root/src/vppinfra/maplog.c
blob: 689133651307b58586422853ad0180e88671fc34 (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
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
/*
 * 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 <vppinfra/maplog.h>

/**
 * @brief Initialize a maplog object
 *
 * Compute record and file size parameters
 * Create and map two log segments to seed the process
 *
 * @param[in/out] a   init args structure
 * @return    0 => success, <0 => failure
 */
int
clib_maplog_init (clib_maplog_init_args_t * a)
{
  int i, fd, limit;
  int rv = 0;
  u8 zero = 0;
  u32 record_size_in_cache_lines;
  u64 file_size_in_records;
  clib_maplog_main_t *mm;
  clib_maplog_header_t _h, *h = &_h;

  ASSERT (a && a->mm);
  mm = a->mm;

  /* Already initialized? */
  if (mm->flags & CLIB_MAPLOG_FLAG_INIT)
    return (-2);

  clib_memset (mm, 0, sizeof (*mm));

  record_size_in_cache_lines =
    (a->record_size_in_bytes + CLIB_CACHE_LINE_BYTES -
     1) / CLIB_CACHE_LINE_BYTES;

  file_size_in_records = a->file_size_in_bytes
    / (record_size_in_cache_lines * CLIB_CACHE_LINE_BYTES);

  /* Round up file size in records to a power of 2, for speed... */
  mm->log2_file_size_in_records = max_log2 (file_size_in_records);
  file_size_in_records = 1ULL << (mm->log2_file_size_in_records);
  a->file_size_in_bytes = file_size_in_records * record_size_in_cache_lines
    * CLIB_CACHE_LINE_BYTES;

  mm->file_basename = format (0, "%s", a->file_basename);
  if (vec_len (mm->file_basename) > ARRAY_LEN (h->file_basename))
    {
      vec_free (mm->file_basename);
      return -11;
    }

  mm->file_size_in_records = file_size_in_records;
  mm->flags |= CLIB_MAPLOG_FLAG_INIT;
  mm->record_size_in_cachelines = record_size_in_cache_lines;
  limit = 2;
  if (a->maplog_is_circular)
    {
      mm->log2_file_size_in_records = 63;
      mm->flags |= CLIB_MAPLOG_FLAG_CIRCULAR;
      limit = 1;
    }

  /*
   * Map the one and only file for a circular log,
   * two files for a normal log.
   */
  for (i = 0; i < limit; i++)
    {
      mm->filenames[i] = format (0, "%v_%d", mm->file_basename,
				 mm->current_file_index++);
      vec_add1 (mm->filenames[i], 0);

      fd = open ((char *) mm->filenames[i], O_CREAT | O_RDWR | O_TRUNC, 0600);
      if (fd < 0)
	{
	  rv = -3;
	  goto fail;
	}

      if (lseek (fd, a->file_size_in_bytes - 1, SEEK_SET) == (off_t) - 1)
	{
	  rv = -4;
	  goto fail;
	}
      if (write (fd, &zero, 1) != 1)
	{
	  rv = -5;
	  goto fail;
	}

      mm->file_baseva[i] = mmap (0, a->file_size_in_bytes,
				 PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
      if (mm->file_baseva[i] == (u8 *) MAP_FAILED)
	{
	  clib_unix_warning ("mmap");
	  goto fail;
	}
      (void) close (fd);
    }

  clib_memset (h, 0, sizeof (*h));
  h->maplog_major_version = MAPLOG_MAJOR_VERSION;
  h->maplog_minor_version = MAPLOG_MINOR_VERSION;
  h->maplog_patch_version = MAPLOG_PATCH_VERSION;
  h->application_id = a->application_id;
  h->application_major_version = a->application_major_version;
  h->application_minor_version = a->application_minor_version;
  h->application_patch_version = a->application_patch_version;
  h->record_size_in_cachelines = record_size_in_cache_lines;
  h->cacheline_size = CLIB_CACHE_LINE_BYTES;
  h->file_size_in_records = file_size_in_records;
  h->number_of_records = ~0ULL;
  h->number_of_files = ~0ULL;
  h->maplog_flag_circular = a->maplog_is_circular;
  memcpy (h->file_basename, mm->file_basename, vec_len (mm->file_basename));

  mm->header_filename = format (0, "%v_header", mm->file_basename);
  vec_add1 (mm->header_filename, 0);

  fd = open ((char *) mm->header_filename, O_CREAT | O_RDWR | O_TRUNC, 0600);
  if (fd < 0)
    {
      clib_unix_warning ("header create");
      rv = -6;
      goto fail;
    }
  rv = write (fd, h, sizeof (*h));
  if (rv != sizeof (*h))
    {
      clib_unix_warning ("header write");
      rv = -7;
      goto fail;
    }
  (void) close (fd);
  return 0;

fail:
  if (fd >= 0)
    (void) close (fd);

  for (i = 0; i < limit; i++)
    {
      if (mm->file_baseva[i])
	(void) munmap ((u8 *) mm->file_baseva[i], a->file_size_in_bytes);
      if (mm->filenames[i])
	(void) unlink ((char *) mm->filenames[i]);
      vec_free (mm->filenames[i]);
    }
  if (mm->header_filename)
    {
      (void) unlink ((char *) mm->header_filename);
      vec_free (mm->header_filename);
    }
  return rv;
}

/* slow path: unmap a full log segment, and replace it */

u8 *
_clib_maplog_get_entry_slowpath (clib_maplog_main_t * mm, u64 my_record_index)
{
  int fd;
  u8 *rv;
  u8 zero = 0;
  u32 unmap_index = (mm->current_file_index) & 1;
  u64 file_size_in_bytes = mm->file_size_in_records
    * mm->record_size_in_cachelines * CLIB_CACHE_LINE_BYTES;

  /* This should never happen */
  ASSERT ((mm->flags & CLIB_MAPLOG_FLAG_CIRCULAR) == 0);

  /*
   * Kill some time by calling format before we make the previous log
   * segment disappear. Obviously it won't do to call clib_maplog_get_entry(),
   * wait 100ms, and then fill in the log entry.
   */
  vec_reset_length (mm->filenames[unmap_index]);
  mm->filenames[unmap_index] = format (mm->filenames[unmap_index],
				       "%v_%d", mm->file_basename,
				       mm->current_file_index++);

  /* Unmap the previous (full) segment */
  (void) munmap ((u8 *) mm->file_baseva[unmap_index], file_size_in_bytes);

  /* Create a new segment */
  fd = open ((char *) mm->filenames[unmap_index],
	     O_CREAT | O_RDWR | O_TRUNC, 0600);

  /* This is not real error recovery... */
  if (fd < 0)
    {
      clib_unix_warning ("creat");
      abort ();
    }

  if (lseek (fd, file_size_in_bytes - 1, SEEK_SET) == (off_t) - 1)
    {
      clib_unix_warning ("lseek");
      abort ();
    }
  if (write (fd, &zero, 1) != 1)
    {
      clib_unix_warning ("set-size write");
      abort ();
    }

  mm->file_baseva[unmap_index] =
    mmap (0, file_size_in_bytes, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
  if (mm->file_baseva[unmap_index] == (u8 *) MAP_FAILED)
    {
      clib_unix_warning ("mmap");
      abort ();
    }
  (void) close (fd);

  rv = (u8 *)
    mm->file_baseva[(my_record_index >> mm->log2_file_size_in_records) & 1] +
    (my_record_index & (mm->file_size_in_records - 1))
    * mm->record_size_in_cachelines * CLIB_CACHE_LINE_BYTES;

  return rv;
}

/**
 * @brief Update a mapped log header file
 *
 * Read the log header. Update the number of records, and number of files
 * @param[in/out] mm	mapped log object
 */
void
clib_maplog_update_header (clib_maplog_main_t * mm)
{
  int fd, rv;
  clib_maplog_header_t _h, *h = &_h;

  if (!(mm->flags & CLIB_MAPLOG_FLAG_INIT))
    return;

  /* Open the log header */
  fd = open ((char *) mm->header_filename, O_RDWR, 0600);
  if (fd < 0)
    {
      clib_unix_warning ("reopen maplog header");
      goto out;
    }

  /* Read the log */
  rv = read (fd, h, sizeof (*h));
  if (rv != sizeof (*h))
    {
      clib_unix_warning ("read maplog header");
      goto out;
    }
  /* Fix the header... */
  h->number_of_records = mm->next_record_index;
  h->number_of_files = mm->current_file_index;
  h->maplog_flag_wrapped = (mm->flags & CLIB_MAPLOG_FLAG_WRAPPED) ? 1 : 0;

  /* Back to the beginning of the log header... */
  if (lseek (fd, 0, SEEK_SET) < 0)
    {
      clib_unix_warning ("lseek to rewrite header");
      goto out;
    }
  /* Rewrite the log header */
  rv = write (fd, h, sizeof (*h));
  if (rv != sizeof (*h))
    clib_unix_warning ("rewrite header");

out:
  if (fd >= 0)
    (void) close (fd);
}

/**
 * @brief Close a mapped log, and update the log header file
 *
 * Unmap the current log segments.
 * Read the log header. Update the number of records, and number of files
 *
 * @param[in/out] mm	mapped log object
 */
void
clib_maplog_close (clib_maplog_main_t * mm)
{
  int i, limit;
  u64 file_size_in_bytes;

  if (!(mm->flags & CLIB_MAPLOG_FLAG_INIT))
    return;

  clib_maplog_update_header (mm);

  file_size_in_bytes =
    mm->file_size_in_records * mm->record_size_in_cachelines *
    CLIB_CACHE_LINE_BYTES;

  limit = (mm->flags & CLIB_MAPLOG_FLAG_CIRCULAR) ? 1 : 2;

  /* unmap current + next segments */
  for (i = 0; i < limit; i++)
    {
      (void) munmap ((u8 *) mm->file_baseva[i], file_size_in_bytes);
      vec_free (mm->filenames[i]);
    }

  vec_free (mm->file_basename);
  vec_free (mm->header_filename);
  clib_memset (mm, 0, sizeof (*mm));
}

/**
 * @brief format a log header
 *
 * Usage: s = format (0, "%U", format_maplog_header, headerp, verbose);
 * @param [in] h clib_maplog_header_t pointer
 * @param [in] verbose self-explanatory
 */
u8 *
format_maplog_header (u8 * s, va_list * args)
{
  clib_maplog_header_t *h = va_arg (*args, clib_maplog_header_t *);
  int verbose = va_arg (*args, int);

  if (!verbose)
    goto brief;
  s = format (s, "basename %s ", h->file_basename);
  s = format (s, "log ver %d.%d.%d app id %u ver %d.%d.%d %s %s\n",
	      h->maplog_major_version,
	      h->maplog_minor_version,
	      h->maplog_patch_version,
	      h->application_id,
	      h->application_major_version,
	      h->application_minor_version, h->application_patch_version,
	      h->maplog_flag_circular ? "circular" : "linear",
	      h->maplog_flag_wrapped ? "wrapped" : "not wrapped");
  s = format (s, "  records are %d %d-byte cachelines\n",
	      h->record_size_in_cachelines, h->cacheline_size);
  s = format (s, "  files are %lld records long, %lld files\n",
	      h->file_size_in_records, h->number_of_files);
  s = format (s, "  %lld records total\n", h->number_of_records);
  return s;

brief:
  s = format (s, "%s %lld records %lld files %lld records/file",
	      h->file_basename, h->number_of_records, h->number_of_files,
	      h->file_size_in_records);
  return s;
}

/**
 * @brief Process a complete maplog
 *
 * Reads the maplog header. Map and process all log segments in order.
 * Calls the callback function once per file with a record count.
 *
 * Note: if the file header isn't updated by calling
 * clib_maplog_close(), it will appear to have an infinite
 * number of records in an infinite number of files.
 *
 * So long as the callback function understands that possibility
 * - by simply ignoring NULL records - the scheme still
 * works...
 *
 * @param [in] file_basename Same basename supplied to clib_maplog_init
 * @param [in] fp_arg Callback function pointer
 */
int
clib_maplog_process (char *file_basename, void *fp_arg)
{
  clib_maplog_header_t _h, *h = &_h;
  int fd, rv = 0;
  u64 file_index;
  u64 file_size_in_bytes;
  u8 *header_filename, *this_filename = 0;
  u8 *file_baseva;
  int (*fp) (clib_maplog_header_t *, void *data, u64 count);
  u64 records_this_file, records_left;
  ASSERT (fp_arg);

  fp = fp_arg;

  header_filename = format (0, "%s_header%c", file_basename, 0);

  fd = open ((char *) header_filename, O_RDONLY, 0600);
  if (fd < 0)
    {
      clib_unix_warning ("open maplog header");
      rv = -1;
      goto out;
    }
  rv = read (fd, h, sizeof (*h));
  if (rv != sizeof (*h))
    {
      clib_unix_warning ("read maplog header");
      rv = -2;
      goto out;
    }
  (void) close (fd);
  fd = -1;

  file_size_in_bytes = h->file_size_in_records
    * h->record_size_in_cachelines * CLIB_CACHE_LINE_BYTES;

  records_left = h->number_of_records;

  for (file_index = 0; file_index < h->number_of_files; file_index++)
    {
      vec_reset_length (this_filename);
      this_filename = format (this_filename, "%s_%llu%c", file_basename,
			      file_index, 0);
      fd = open ((char *) this_filename, O_RDONLY, 0600);
      if (fd < 0)
	{
	  rv = -3;
	  goto out;
	}

      file_baseva =
	mmap (0, file_size_in_bytes, PROT_READ, MAP_SHARED, fd, 0);
      (void) close (fd);
      fd = -1;
      if (file_baseva == (u8 *) MAP_FAILED)
	{
	  clib_unix_warning ("mmap");
	  rv = -4;
	  goto out;
	}

      records_this_file = (records_left > h->file_size_in_records) ?
	h->file_size_in_records : records_left;

      /*
       * Normal log, or a circular non-wrapped log, or a circular
       * wrapped log which happens to be exactly linear
       */
      if (h->maplog_flag_circular == 0 || h->maplog_flag_wrapped == 0 ||
	  ((h->number_of_records % h->file_size_in_records) == 0))
	(*fp) (h, file_baseva, records_this_file);
      else
	{
	  /* "Normal" wrapped circular log */
	  u64 first_chunk_record_index = h->number_of_records &
	    (h->file_size_in_records - 1);
	  u64 first_chunk_number_of_records = records_this_file -
	    first_chunk_record_index;
	  u8 *chunk_baseva = file_baseva +
	    first_chunk_record_index * h->record_size_in_cachelines *
	    h->cacheline_size;
	  (*fp) (h, chunk_baseva, first_chunk_number_of_records);
	  (*fp) (h, file_baseva,
		 records_this_file - first_chunk_number_of_records);
	}

      if (munmap (file_baseva, file_size_in_bytes) < 0)
	{
	  clib_warning ("munmap");
	  rv = -5;
	  /* but don't stop... */
	}
      records_left -= records_this_file;
      if (records_left == 0)
	break;
    }

out:
  if (fd >= 0)
    (void) close (fd);

  vec_free (this_filename);
  vec_free (header_filename);
  return rv;
}


/*
 * fd.io coding-style-patch-verification: ON
 *
 * Local Variables:
 * eval: (c-set-style "gnu")
 * End:
 */
class="n">claddr[0]) self.assertEqual(data[9], claddr[1]) self.assertEqual(data[10], claddr[2]) self.assertEqual(data[11], claddr[3]) if oui != 0: # sub-option 151 encodes vss_type 1, # the 3 byte oui and the 4 byte fib_id self.assertEqual(id_len, 0) self.assertEqual(six.byte2int(data[12:13]), 151) self.assertEqual(six.byte2int(data[13:14]), 8) self.assertEqual(six.byte2int(data[14:15]), 1) self.assertEqual(six.byte2int(data[15:16]), 0) self.assertEqual(six.byte2int(data[16:17]), 0) self.assertEqual(six.byte2int(data[17:18]), oui) self.assertEqual(six.byte2int(data[18:19]), 0) self.assertEqual(six.byte2int(data[19:20]), 0) self.assertEqual(six.byte2int(data[20:21]), 0) self.assertEqual(six.byte2int(data[21:22]), fib_id) # VSS control sub-option self.assertEqual(six.byte2int(data[22:23]), 152) self.assertEqual(six.byte2int(data[23:24]), 0) if id_len > 0: # sub-option 151 encode vss_type of 0 # followerd by vpn_id in ascii self.assertEqual(oui, 0) self.assertEqual(six.byte2int(data[12:13]), 151) self.assertEqual(six.byte2int(data[13:14]), id_len + 1) self.assertEqual(six.byte2int(data[14:15]), 0) self.assertEqual(data[15:15 + id_len].decode('ascii'), vpn_id) # VSS control sub-option self.assertEqual(six.byte2int(data[15 + len(vpn_id): 16 + len(vpn_id)]), 152) self.assertEqual(six.byte2int(data[16 + len(vpn_id): 17 + len(vpn_id)]), 0) found = 1 self.assertTrue(found) return data def verify_dhcp_msg_type(self, pkt, name): dhcp = pkt[DHCP] found = False for o in dhcp.options: if isinstance(o, tuple): if o[0] == "message-type" \ and DHCPTypes[o[1]] == name: found = True self.assertTrue(found) def verify_dhcp_offer(self, pkt, intf, vpn_id="", fib_id=0, oui=0): ether = pkt[Ether] self.assertEqual(ether.dst, "ff:ff:ff:ff:ff:ff") self.assertEqual(ether.src, intf.local_mac) ip = pkt[IP] self.assertEqual(ip.dst, "255.255.255.255") self.assertEqual(ip.src, intf.local_ip4) udp = pkt[UDP] self.assertEqual(udp.dport, DHCP4_CLIENT_PORT) self.assertEqual(udp.sport, DHCP4_SERVER_PORT) self.verify_dhcp_msg_type(pkt, "offer") data = self.validate_relay_options(pkt, intf, intf.local_ip4, vpn_id, fib_id, oui) def verify_orig_dhcp_pkt(self, pkt, intf, dscp, l2_bc=True): ether = pkt[Ether] if l2_bc: self.assertEqual(ether.dst, "ff:ff:ff:ff:ff:ff") else: self.assertEqual(ether.dst, intf.remote_mac) self.assertEqual(ether.src, intf.local_mac) ip = pkt[IP] if (l2_bc): self.assertEqual(ip.dst, "255.255.255.255") self.assertEqual(ip.src, "0.0.0.0") else: self.assertEqual(ip.dst, intf.remote_ip4) self.assertEqual(ip.src, intf.local_ip4) self.assertEqual(ip.tos, dscp) udp = pkt[UDP] 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, broadcast=True, dscp=0): self.verify_orig_dhcp_pkt(pkt, intf, dscp) self.verify_dhcp_msg_type(pkt, "discover") self.verify_dhcp_has_option(pkt, "hostname", hostname.encode('ascii')) if client_id: client_id = '\x00' + client_id self.verify_dhcp_has_option(pkt, "client_id", client_id.encode('ascii')) bootp = pkt[BOOTP] self.assertEqual(bootp.ciaddr, "0.0.0.0") self.assertEqual(bootp.giaddr, "0.0.0.0") if broadcast: self.assertEqual(bootp.flags, 0x8000) else: self.assertEqual(bootp.flags, 0x0000) def verify_orig_dhcp_request(self, pkt, intf, hostname, ip, broadcast=True, l2_bc=True, dscp=0): self.verify_orig_dhcp_pkt(pkt, intf, dscp, l2_bc=l2_bc) self.verify_dhcp_msg_type(pkt, "request") self.verify_dhcp_has_option(pkt, "hostname", hostname.encode('ascii')) self.verify_dhcp_has_option(pkt, "requested_addr", ip) bootp = pkt[BOOTP] if l2_bc: self.assertEqual(bootp.ciaddr, "0.0.0.0") else: self.assertEqual(bootp.ciaddr, intf.local_ip4) self.assertEqual(bootp.giaddr, "0.0.0.0") 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, vpn_id="", dst_mac=None, dst_ip=None): if not dst_mac: dst_mac = intf.remote_mac if not dst_ip: dst_ip = intf.remote_ip4 ether = pkt[Ether] self.assertEqual(ether.dst, dst_mac) self.assertEqual(ether.src, intf.local_mac) ip = pkt[IP] self.assertEqual(ip.dst, dst_ip) self.assertEqual(ip.src, intf.local_ip4) udp = pkt[UDP] self.assertEqual(udp.dport, DHCP4_SERVER_PORT) self.assertEqual(udp.sport, DHCP4_CLIENT_PORT) dhcp = pkt[DHCP] is_discover = False for o in dhcp.options: if isinstance(o, tuple): if o[0] == "message-type" \ and DHCPTypes[o[1]] == "discover": is_discover = True self.assertTrue(is_discover) data = self.validate_relay_options(pkt, src_intf, src_intf.local_ip4, vpn_id, fib_id, oui) return data def verify_dhcp6_solicit(self, pkt, intf, peer_ip, peer_mac, vpn_id="", fib_id=0, oui=0, dst_mac=None, dst_ip=None): if not dst_mac: dst_mac = intf.remote_mac if not dst_ip: dst_ip = in6_ptop(intf.remote_ip6) ether = pkt[Ether] self.assertEqual(ether.dst, dst_mac) self.assertEqual(ether.src, intf.local_mac) ip = pkt[IPv6] self.assertEqual(in6_ptop(ip.dst), dst_ip) self.assertEqual(in6_ptop(ip.src), in6_ptop(intf.local_ip6)) udp = pkt[UDP] self.assertEqual(udp.dport, DHCP6_CLIENT_PORT) self.assertEqual(udp.sport, DHCP6_SERVER_PORT) relay = pkt[DHCP6_RelayForward] self.assertEqual(in6_ptop(relay.peeraddr), in6_ptop(peer_ip)) oid = pkt[DHCP6OptIfaceId] cll = pkt[DHCP6OptClientLinkLayerAddr] self.assertEqual(cll.optlen, 8) self.assertEqual(cll.lltype, 1) self.assertEqual(cll.clladdr, peer_mac) id_len = len(vpn_id) if fib_id != 0: self.assertEqual(id_len, 0) vss = pkt[DHCP6OptVSS] self.assertEqual(vss.optlen, 8) self.assertEqual(vss.type, 1) # the OUI and FIB-id are really 3 and 4 bytes resp. # but the tested range is small self.assertEqual(six.byte2int(vss.data[0:1]), 0) self.assertEqual(six.byte2int(vss.data[1:2]), 0) self.assertEqual(six.byte2int(vss.data[2:3]), oui) self.assertEqual(six.byte2int(vss.data[3:4]), 0) self.assertEqual(six.byte2int(vss.data[4:5]), 0) self.assertEqual(six.byte2int(vss.data[5:6]), 0) self.assertEqual(six.byte2int(vss.data[6:7]), fib_id) if id_len > 0: self.assertEqual(oui, 0) vss = pkt[DHCP6OptVSS] self.assertEqual(vss.optlen, id_len + 1) self.assertEqual(vss.type, 0) self.assertEqual(vss.data[0:id_len].decode('ascii'), vpn_id) # the relay message should be an encoded Solicit msg = pkt[DHCP6OptRelayMsg] sol = DHCP6_Solicit() self.assertEqual(msg.optlen, len(sol)) self.assertEqual(sol, msg[1]) def verify_dhcp6_advert(self, pkt, intf, peer): ether = pkt[Ether] self.assertEqual(ether.dst, "ff:ff:ff:ff:ff:ff") self.assertEqual(ether.src, intf.local_mac) ip = pkt[IPv6] self.assertEqual(in6_ptop(ip.dst), in6_ptop(peer)) self.assertEqual(in6_ptop(ip.src), in6_ptop(intf.local_ip6)) udp = pkt[UDP] self.assertEqual(udp.dport, DHCP6_SERVER_PORT) self.assertEqual(udp.sport, DHCP6_CLIENT_PORT) # not sure why this is not decoding # adv = pkt[DHCP6_Advertise] def wait_for_no_route(self, address, length, n_tries=50, s_time=1): while (n_tries): if not find_route(self, address, length): return True n_tries = n_tries - 1 self.sleep(s_time) return False def test_dhcp_proxy(self): """ DHCPv4 Proxy """ # # Verify no response to DHCP request without DHCP config # p_disc_vrf0 = (Ether(dst="ff:ff:ff:ff:ff:ff", src=self.pg3.remote_mac) / IP(src="0.0.0.0", dst="255.255.255.255") / UDP(sport=DHCP4_CLIENT_PORT, dport=DHCP4_SERVER_PORT) / BOOTP(op=1) / DHCP(options=[('message-type', 'discover'), ('end')])) pkts_disc_vrf0 = [p_disc_vrf0] p_disc_vrf1 = (Ether(dst="ff:ff:ff:ff:ff:ff", src=self.pg4.remote_mac) / IP(src="0.0.0.0", dst="255.255.255.255") / UDP(sport=DHCP4_CLIENT_PORT, dport=DHCP4_SERVER_PORT) / BOOTP(op=1) / DHCP(options=[('message-type', 'discover'), ('end')])) pkts_disc_vrf1 = [p_disc_vrf1] p_disc_vrf2 = (Ether(dst="ff:ff:ff:ff:ff:ff", src=self.pg5.remote_mac) / IP(src="0.0.0.0", dst="255.255.255.255") / UDP(sport=DHCP4_CLIENT_PORT, dport=DHCP4_SERVER_PORT) / BOOTP(op=1) / DHCP(options=[('message-type', 'discover'), ('end')])) pkts_disc_vrf2 = [p_disc_vrf2] self.send_and_assert_no_replies(self.pg3, pkts_disc_vrf0, "DHCP with no configuration") self.send_and_assert_no_replies(self.pg4, pkts_disc_vrf1, "DHCP with no configuration") self.send_and_assert_no_replies(self.pg5, pkts_disc_vrf2, "DHCP with no configuration") # # Enable DHCP proxy in VRF 0 # server_addr = self.pg0.remote_ip4 src_addr = self.pg0.local_ip4 Proxy = VppDHCPProxy(self, server_addr, src_addr, rx_vrf_id=0) Proxy.add_vpp_config() # # Discover packets from the client are dropped because there is no # IP address configured on the client facing interface # self.send_and_assert_no_replies(self.pg3, pkts_disc_vrf0, "Discover DHCP no relay address") # # Inject a response from the server # dropped, because there is no IP addrees on the # client interfce to fill in the option. # p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) / IP(src=self.pg0.remote_ip4, dst=self.pg0.local_ip4) / UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT) / BOOTP(op=1) / DHCP(options=[('message-type', 'offer'), ('end')])) pkts = [p] self.send_and_assert_no_replies(self.pg3, pkts, "Offer DHCP no relay address") # # configure an IP address on the client facing interface # self.pg3.config_ip4() # # Try again with a discover packet # Rx'd packet should be to the server address and from the configured # source address # UDP source ports are unchanged # we've no option 82 config so that should be absent # self.pg3.add_stream(pkts_disc_vrf0) self.pg_enable_capture(self.pg_interfaces) self.pg_start() rx = self.pg0.get_capture(1) rx = rx[0] option_82 = self.verify_relayed_dhcp_discover(rx, self.pg0, src_intf=self.pg3) # # Create an DHCP offer reply from the server with a correctly formatted # option 82. i.e. send back what we just captured # The offer, sent mcast to the client, still has option 82. # p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) / IP(src=self.pg0.remote_ip4, dst=self.pg0.local_ip4) / UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT) / BOOTP(op=1) / DHCP(options=[('message-type', 'offer'), ('relay_agent_Information', option_82), ('end')])) pkts = [p] self.pg0.add_stream(pkts) self.pg_enable_capture(self.pg_interfaces) self.pg_start() rx = self.pg3.get_capture(1) rx = rx[0] self.verify_dhcp_offer(rx, self.pg3) # # Bogus Option 82: # # 1. not our IP address = not checked by VPP? so offer is replayed # to client bad_ip = option_82[0:8] + scapy.compat.chb(33) + option_82[9:] p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) / IP(src=self.pg0.remote_ip4, dst=self.pg0.local_ip4) / UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT) / BOOTP(op=1) / DHCP(options=[('message-type', 'offer'), ('relay_agent_Information', bad_ip), ('end')])) pkts = [p] self.send_and_assert_no_replies(self.pg0, pkts, "DHCP offer option 82 bad address") # 2. Not a sw_if_index VPP knows bad_if_index = option_82[0:2] + scapy.compat.chb(33) + option_82[3:] p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) / IP(src=self.pg0.remote_ip4, dst=self.pg0.local_ip4) / UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT) / BOOTP(op=1) / DHCP(options=[('message-type', 'offer'), ('relay_agent_Information', bad_if_index), ('end')])) pkts = [p] self.send_and_assert_no_replies(self.pg0, pkts, "DHCP offer option 82 bad if index") # # Send a DHCP request in VRF 1. should be dropped. # self.send_and_assert_no_replies(self.pg4, pkts_disc_vrf1, "DHCP with no configuration VRF 1") # # Delete the DHCP config in VRF 0 # Should now drop requests. # Proxy.remove_vpp_config() self.send_and_assert_no_replies(self.pg3, pkts_disc_vrf0, "DHCP config removed VRF 0") self.send_and_assert_no_replies(self.pg4, pkts_disc_vrf1, "DHCP config removed VRF 1") # # Add DHCP config for VRF 1 & 2 # server_addr1 = self.pg1.remote_ip4 src_addr1 = self.pg1.local_ip4 Proxy1 = VppDHCPProxy( self, server_addr1, src_addr1, rx_vrf_id=1, server_vrf_id=1) Proxy1.add_vpp_config() server_addr2 = self.pg2.remote_ip4 src_addr2 = self.pg2.local_ip4 Proxy2 = VppDHCPProxy( self, server_addr2, src_addr2, rx_vrf_id=2, server_vrf_id=2) Proxy2.add_vpp_config() # # Confim DHCP requests ok in VRF 1 & 2. # - dropped on IP config on client interface # self.send_and_assert_no_replies(self.pg4, pkts_disc_vrf1, "DHCP config removed VRF 1") self.send_and_assert_no_replies(self.pg5, pkts_disc_vrf2, "DHCP config removed VRF 2") # # configure an IP address on the client facing interface # self.pg4.config_ip4() self.pg4.add_stream(pkts_disc_vrf1) self.pg_enable_capture(self.pg_interfaces) self.pg_start() rx = self.pg1.get_capture(1) rx = rx[0] self.verify_relayed_dhcp_discover(rx, self.pg1, src_intf=self.pg4) self.pg5.config_ip4() self.pg5.add_stream(pkts_disc_vrf2) self.pg_enable_capture(self.pg_interfaces) self.pg_start() rx = self.pg2.get_capture(1) rx = rx[0] self.verify_relayed_dhcp_discover(rx, self.pg2, src_intf=self.pg5) # # Add VSS config # table=1, vss_type=1, vpn_index=1, oui=4 # table=2, vss_type=0, vpn_id = "ip4-table-2" self.vapi.dhcp_proxy_set_vss(tbl_id=1, vss_type=1, vpn_index=1, oui=4, is_add=1) self.vapi.dhcp_proxy_set_vss(tbl_id=2, vss_type=0, vpn_ascii_id="ip4-table-2", is_add=1) self.pg4.add_stream(pkts_disc_vrf1) self.pg_enable_capture(self.pg_interfaces) self.pg_start() rx = self.pg1.get_capture(1) rx = rx[0] self.verify_relayed_dhcp_discover(rx, self.pg1, src_intf=self.pg4, fib_id=1, oui=4) self.pg5.add_stream(pkts_disc_vrf2) self.pg_enable_capture(self.pg_interfaces) self.pg_start() rx = self.pg2.get_capture(1) rx = rx[0] self.verify_relayed_dhcp_discover(rx, self.pg2, src_intf=self.pg5, vpn_id="ip4-table-2") # # Add a second DHCP server in VRF 1 # expect clients messages to be relay to both configured servers # self.pg1.generate_remote_hosts(2) server_addr12 = self.pg1.remote_hosts[1].ip4 Proxy12 = VppDHCPProxy( self, server_addr12, src_addr, rx_vrf_id=1, server_vrf_id=1) Proxy12.add_vpp_config() # # We'll need an ARP entry for the server to send it packets # arp_entry = VppNeighbor(self, self.pg1.sw_if_index, self.pg1.remote_hosts[1].mac, self.pg1.remote_hosts[1].ip4) arp_entry.add_vpp_config() # # Send a discover from the client. expect two relayed messages # The frist packet is sent to the second server # We're not enforcing that here, it's just the way it is. # self.pg4.add_stream(pkts_disc_vrf1) self.pg_enable_capture(self.pg_interfaces) self.pg_start() rx = self.pg1.get_capture(2) option_82 = self.verify_relayed_dhcp_discover( rx[0], self.pg1, src_intf=self.pg4, dst_mac=self.pg1.remote_hosts[1].mac, dst_ip=self.pg1.remote_hosts[1].ip4, fib_id=1, oui=4) self.verify_relayed_dhcp_discover(rx[1], self.pg1, src_intf=self.pg4, fib_id=1, oui=4) # # Send both packets back. Client gets both. # p1 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) / IP(src=self.pg1.remote_ip4, dst=self.pg1.local_ip4) / UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT) / BOOTP(op=1) / DHCP(options=[('message-type', 'offer'), ('relay_agent_Information', option_82), ('end')])) p2 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) / IP(src=self.pg1.remote_hosts[1].ip4, dst=self.pg1.local_ip4) / UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT) / BOOTP(op=1) / DHCP(options=[('message-type', 'offer'), ('relay_agent_Information', option_82), ('end')])) pkts = [p1, p2] self.pg1.add_stream(pkts) self.pg_enable_capture(self.pg_interfaces) self.pg_start() rx = self.pg4.get_capture(2) self.verify_dhcp_offer(rx[0], self.pg4, fib_id=1, oui=4) self.verify_dhcp_offer(rx[1], self.pg4, fib_id=1, oui=4) # # Ensure offers from non-servers are dropeed # p2 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) / IP(src="8.8.8.8", dst=self.pg1.local_ip4) / UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT) / BOOTP(op=1) / DHCP(options=[('message-type', 'offer'), ('relay_agent_Information', option_82), ('end')])) self.send_and_assert_no_replies(self.pg1, p2, "DHCP offer from non-server") # # Ensure only the discover is sent to multiple servers # p_req_vrf1 = (Ether(dst="ff:ff:ff:ff:ff:ff", src=self.pg4.remote_mac) / IP(src="0.0.0.0", dst="255.255.255.255") / UDP(sport=DHCP4_CLIENT_PORT, dport=DHCP4_SERVER_PORT) / BOOTP(op=1) / DHCP(options=[('message-type', 'request'), ('end')])) self.pg4.add_stream(p_req_vrf1) self.pg_enable_capture(self.pg_interfaces) self.pg_start() rx = self.pg1.get_capture(1) # # Remove the second DHCP server # Proxy12.remove_vpp_config() # # Test we can still relay with the first # self.pg4.add_stream(pkts_disc_vrf1) self.pg_enable_capture(self.pg_interfaces) self.pg_start() rx = self.pg1.get_capture(1) rx = rx[0] self.verify_relayed_dhcp_discover(rx, self.pg1, src_intf=self.pg4, fib_id=1, oui=4) # # Remove the VSS config # relayed DHCP has default vlaues in the option. # self.vapi.dhcp_proxy_set_vss(tbl_id=1, is_add=0) self.vapi.dhcp_proxy_set_vss(tbl_id=2, is_add=0) self.pg4.add_stream(pkts_disc_vrf1) self.pg_enable_capture(self.pg_interfaces) self.pg_start() rx = self.pg1.get_capture(1) rx = rx[0] self.verify_relayed_dhcp_discover(rx, self.pg1, src_intf=self.pg4) # # remove DHCP config to cleanup # Proxy1.remove_vpp_config() Proxy2.remove_vpp_config() self.send_and_assert_no_replies(self.pg3, pkts_disc_vrf0, "DHCP cleanup VRF 0") self.send_and_assert_no_replies(self.pg4, pkts_disc_vrf1, "DHCP cleanup VRF 1") self.send_and_assert_no_replies(self.pg5, pkts_disc_vrf2, "DHCP cleanup VRF 2") self.pg3.unconfig_ip4() self.pg4.unconfig_ip4() self.pg5.unconfig_ip4() def test_dhcp6_proxy(self): """ DHCPv6 Proxy""" # # Verify no response to DHCP request without DHCP config # dhcp_solicit_dst = "ff02::1:2" dhcp_solicit_src_vrf0 = mk_ll_addr(self.pg3.remote_mac) dhcp_solicit_src_vrf1 = mk_ll_addr(self.pg4.remote_mac) dhcp_solicit_src_vrf2 = mk_ll_addr(self.pg5.remote_mac) server_addr_vrf0 = self.pg0.remote_ip6 src_addr_vrf0 = self.pg0.local_ip6 server_addr_vrf1 = self.pg1.remote_ip6 src_addr_vrf1 = self.pg1.local_ip6 server_addr_vrf2 = self.pg2.remote_ip6 src_addr_vrf2 = self.pg2.local_ip6 dmac = in6_getnsmac(inet_pton(socket.AF_INET6, dhcp_solicit_dst)) p_solicit_vrf0 = (Ether(dst=dmac, src=self.pg3.remote_mac) / IPv6(src=dhcp_solicit_src_vrf0, dst=dhcp_solicit_dst) / UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_CLIENT_PORT) / DHCP6_Solicit()) p_solicit_vrf1 = (Ether(dst=dmac, src=self.pg4.remote_mac) / IPv6(src=dhcp_solicit_src_vrf1, dst=dhcp_solicit_dst) / UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_CLIENT_PORT) / DHCP6_Solicit()) p_solicit_vrf2 = (Ether(dst=dmac, src=self.pg5.remote_mac) / IPv6(src=dhcp_solicit_src_vrf2, dst=dhcp_solicit_dst) / UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_CLIENT_PORT) / DHCP6_Solicit()) self.send_and_assert_no_replies(self.pg3, p_solicit_vrf0, "DHCP with no configuration") self.send_and_assert_no_replies(self.pg4, p_solicit_vrf1, "DHCP with no configuration") self.send_and_assert_no_replies(self.pg5, p_solicit_vrf2, "DHCP with no configuration") # # DHCPv6 config in VRF 0. # Packets still dropped because the client facing interface has no # IPv6 config # Proxy = VppDHCPProxy( self, server_addr_vrf0, src_addr_vrf0, rx_vrf_id=0, server_vrf_id=0) Proxy.add_vpp_config() self.send_and_assert_no_replies(self.pg3, p_solicit_vrf0, "DHCP with no configuration") self.send_and_assert_no_replies(self.pg4, p_solicit_vrf1, "DHCP with no configuration") # # configure an IP address on the client facing interface # self.pg3.config_ip6() # # Now the DHCP requests are relayed to the server # self.pg3.add_stream(p_solicit_vrf0) self.pg_enable_capture(self.pg_interfaces) self.pg_start() rx = self.pg0.get_capture(1) self.verify_dhcp6_solicit(rx[0], self.pg0, dhcp_solicit_src_vrf0, self.pg3.remote_mac) # # Exception cases for rejected relay responses # # 1 - not a relay reply p_adv_vrf0 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) / IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6) / UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) / DHCP6_Advertise()) self.send_and_assert_no_replies(self.pg3, p_adv_vrf0, "DHCP6 not a relay reply") # 2 - no relay message option p_adv_vrf0 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) / IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6) / UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) / DHCP6_RelayReply() / DHCP6_Advertise()) self.send_and_assert_no_replies(self.pg3, p_adv_vrf0, "DHCP not a relay message") # 3 - no circuit ID p_adv_vrf0 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) / IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6) / UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) / DHCP6_RelayReply() / DHCP6OptRelayMsg(optlen=0) / DHCP6_Advertise()) self.send_and_assert_no_replies(self.pg3, p_adv_vrf0, "DHCP6 no circuit ID") # 4 - wrong circuit ID p_adv_vrf0 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) / IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6) / UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) / DHCP6_RelayReply() / DHCP6OptIfaceId(optlen=4, ifaceid='\x00\x00\x00\x05') / DHCP6OptRelayMsg(optlen=0) / DHCP6_Advertise()) self.send_and_assert_no_replies(self.pg3, p_adv_vrf0, "DHCP6 wrong circuit ID") # # Send the relay response (the advertisement) # - no peer address p_adv_vrf0 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) / IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6) / UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) / DHCP6_RelayReply() / DHCP6OptIfaceId(optlen=4, ifaceid='\x00\x00\x00\x04') / DHCP6OptRelayMsg(optlen=0) / DHCP6_Advertise(trid=1) / DHCP6OptStatusCode(statuscode=0)) pkts_adv_vrf0 = [p_adv_vrf0] self.pg0.add_stream(pkts_adv_vrf0) self.pg_enable_capture(self.pg_interfaces) self.pg_start() rx = self.pg3.get_capture(1) self.verify_dhcp6_advert(rx[0], self.pg3, "::") # # Send the relay response (the advertisement) # - with peer address p_adv_vrf0 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) / IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6) / UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) / DHCP6_RelayReply(peeraddr=dhcp_solicit_src_vrf0) / DHCP6OptIfaceId(optlen=4, ifaceid='\x00\x00\x00\x04') / DHCP6OptRelayMsg(optlen=0) / DHCP6_Advertise(trid=1) / DHCP6OptStatusCode(statuscode=0)) pkts_adv_vrf0 = [p_adv_vrf0] self.pg0.add_stream(pkts_adv_vrf0) self.pg_enable_capture(self.pg_interfaces) self.pg_start() rx = self.pg3.get_capture(1) self.verify_dhcp6_advert(rx[0], self.pg3, dhcp_solicit_src_vrf0) # # Add all the config for VRF 1 & 2 # Proxy1 = VppDHCPProxy( self, server_addr_vrf1, src_addr_vrf1, rx_vrf_id=1, server_vrf_id=1) Proxy1.add_vpp_config() self.pg4.config_ip6() Proxy2 = VppDHCPProxy( self, server_addr_vrf2, src_addr_vrf2, rx_vrf_id=2, server_vrf_id=2) Proxy2.add_vpp_config() self.pg5.config_ip6() # # VRF 1 solicit # self.pg4.add_stream(p_solicit_vrf1) self.pg_enable_capture(self.pg_interfaces) self.pg_start() rx = self.pg1.get_capture(1) self.verify_dhcp6_solicit(rx[0], self.pg1, dhcp_solicit_src_vrf1, self.pg4.remote_mac) # # VRF 2 solicit # self.pg5.add_stream(p_solicit_vrf2) self.pg_enable_capture(self.pg_interfaces) self.pg_start() rx = self.pg2.get_capture(1) self.verify_dhcp6_solicit(rx[0], self.pg2, dhcp_solicit_src_vrf2, self.pg5.remote_mac) # # VRF 1 Advert # p_adv_vrf1 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) / IPv6(dst=self.pg1.local_ip6, src=self.pg1.remote_ip6) / UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) / DHCP6_RelayReply(peeraddr=dhcp_solicit_src_vrf1) / DHCP6OptIfaceId(optlen=4, ifaceid='\x00\x00\x00\x05') / DHCP6OptRelayMsg(optlen=0) / DHCP6_Advertise(trid=1) / DHCP6OptStatusCode(statuscode=0)) pkts_adv_vrf1 = [p_adv_vrf1] self.pg1.add_stream(pkts_adv_vrf1) self.pg_enable_capture(self.pg_interfaces) self.pg_start() rx = self.pg4.get_capture(1) self.verify_dhcp6_advert(rx[0], self.pg4, dhcp_solicit_src_vrf1) # # Add VSS config # self.vapi.dhcp_proxy_set_vss( tbl_id=1, vss_type=1, oui=4, vpn_index=1, is_ipv6=1, is_add=1) self.vapi.dhcp_proxy_set_vss( tbl_id=2, vss_type=0, vpn_ascii_id="IPv6-table-2", is_ipv6=1, is_add=1) self.pg4.add_stream(p_solicit_vrf1) self.pg_enable_capture(self.pg_interfaces) self.pg_start() rx = self.pg1.get_capture(1) self.verify_dhcp6_solicit(rx[0], self.pg1, dhcp_solicit_src_vrf1, self.pg4.remote_mac, fib_id=1, oui=4) self.pg5.add_stream(p_solicit_vrf2) self.pg_enable_capture(self.pg_interfaces) self.pg_start() rx = self.pg2.get_capture(1) self.verify_dhcp6_solicit(rx[0], self.pg2, dhcp_solicit_src_vrf2, self.pg5.remote_mac, vpn_id="IPv6-table-2") # # Remove the VSS config # relayed DHCP has default vlaues in the option. # self.vapi.dhcp_proxy_set_vss(tbl_id=1, is_ipv6=1, is_add=0) self.pg4.add_stream(p_solicit_vrf1) self.pg_enable_capture(self.pg_interfaces) self.pg_start() rx = self.pg1.get_capture(1) self.verify_dhcp6_solicit(rx[0], self.pg1, dhcp_solicit_src_vrf1, self.pg4.remote_mac) # # Add a second DHCP server in VRF 1 # expect clients messages to be relay to both configured servers # self.pg1.generate_remote_hosts(2) server_addr12 = self.pg1.remote_hosts[1].ip6 Proxy12 = VppDHCPProxy( self, server_addr12, src_addr_vrf1, rx_vrf_id=1, server_vrf_id=1) Proxy12.add_vpp_config() # # We'll need an ND entry for the server to send it packets # nd_entry = VppNeighbor(self, self.pg1.sw_if_index, self.pg1.remote_hosts[1].mac, self.pg1.remote_hosts[1].ip6) nd_entry.add_vpp_config() # # Send a discover from the client. expect two relayed messages # The frist packet is sent to the second server # We're not enforcing that here, it's just the way it is. # self.pg4.add_stream(p_solicit_vrf1) self.pg_enable_capture(self.pg_interfaces) self.pg_start() rx = self.pg1.get_capture(2) self.verify_dhcp6_solicit(rx[0], self.pg1, dhcp_solicit_src_vrf1, self.pg4.remote_mac) self.verify_dhcp6_solicit(rx[1], self.pg1, dhcp_solicit_src_vrf1, self.pg4.remote_mac, dst_mac=self.pg1.remote_hosts[1].mac, dst_ip=self.pg1.remote_hosts[1].ip6) # # Send both packets back. Client gets both. # p1 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) / IPv6(dst=self.pg1.local_ip6, src=self.pg1.remote_ip6) / UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) / DHCP6_RelayReply(peeraddr=dhcp_solicit_src_vrf1) / DHCP6OptIfaceId(optlen=4, ifaceid='\x00\x00\x00\x05') / DHCP6OptRelayMsg(optlen=0) / DHCP6_Advertise(trid=1) / DHCP6OptStatusCode(statuscode=0)) p2 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_hosts[1].mac) / IPv6(dst=self.pg1.local_ip6, src=self.pg1._remote_hosts[1].ip6) / UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) / DHCP6_RelayReply(peeraddr=dhcp_solicit_src_vrf1) / DHCP6OptIfaceId(optlen=4, ifaceid='\x00\x00\x00\x05') / DHCP6OptRelayMsg(optlen=0) / DHCP6_Advertise(trid=1) / DHCP6OptStatusCode(statuscode=0)) pkts = [p1, p2] self.pg1.add_stream(pkts) self.pg_enable_capture(self.pg_interfaces) self.pg_start() rx = self.pg4.get_capture(2) self.verify_dhcp6_advert(rx[0], self.pg4, dhcp_solicit_src_vrf1) self.verify_dhcp6_advert(rx[1], self.pg4, dhcp_solicit_src_vrf1) # # Ensure only solicit messages are duplicated # p_request_vrf1 = (Ether(dst=dmac, src=self.pg4.remote_mac) / IPv6(src=dhcp_solicit_src_vrf1, dst=dhcp_solicit_dst) / UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_CLIENT_PORT) / DHCP6_Request()) self.pg4.add_stream(p_request_vrf1) self.pg_enable_capture(self.pg_interfaces) self.pg_start() rx = self.pg1.get_capture(1) # # Test we drop DHCP packets from addresses that are not configured as # DHCP servers # p2 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_hosts[1].mac) / IPv6(dst=self.pg1.local_ip6, src="3001::1") / UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) / DHCP6_RelayReply(peeraddr=dhcp_solicit_src_vrf1) / DHCP6OptIfaceId(optlen=4, ifaceid='\x00\x00\x00\x05') / DHCP6OptRelayMsg(optlen=0) / DHCP6_Advertise(trid=1) / DHCP6OptStatusCode(statuscode=0)) self.send_and_assert_no_replies(self.pg1, p2, "DHCP6 not from server") # # Remove the second DHCP server # Proxy12.remove_vpp_config() # # Test we can still relay with the first # self.pg4.add_stream(p_solicit_vrf1) self.pg_enable_capture(self.pg_interfaces) self.pg_start() rx = self.pg1.get_capture(1) self.verify_dhcp6_solicit(rx[0], self.pg1, dhcp_solicit_src_vrf1, self.pg4.remote_mac) # # Cleanup # Proxy.remove_vpp_config() Proxy1.remove_vpp_config() Proxy2.remove_vpp_config() self.pg3.unconfig_ip6() self.pg4.unconfig_ip6() self.pg5.unconfig_ip6() def test_dhcp_client(self): """ DHCP Client""" vdscp = VppEnum.vl_api_ip_dscp_t hostname = 'universal-dp' self.pg_enable_capture(self.pg_interfaces) # # Configure DHCP client on PG3 and capture the discover sent # Client = VppDHCPClient(self, self.pg3.sw_if_index, hostname) Client.add_vpp_config() self.assertTrue(Client.query_vpp_config()) rx = self.pg3.get_capture(1) self.verify_orig_dhcp_discover(rx[0], self.pg3, hostname) # # Send back on offer, expect the request # p_offer = (Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac) / IP(src=self.pg3.remote_ip4, dst="255.255.255.255") / UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT) / BOOTP(op=1, yiaddr=self.pg3.local_ip4, chaddr=mac_pton(self.pg3.local_mac)) / 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) # # Send an acknowledgment # p_ack = (Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac) / IP(src=self.pg3.remote_ip4, dst="255.255.255.255") / UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT) / BOOTP(op=1, yiaddr=self.pg3.local_ip4, chaddr=mac_pton(self.pg3.local_mac)) / 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 DHCP config # Client.remove_vpp_config() # # 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)) # # Start the procedure again. this time have VPP send the client-ID # and set the DSCP value # self.pg3.admin_down() self.sleep(1) self.pg3.admin_up() Client.set_client(self.pg3.sw_if_index, hostname, id=self.pg3.local_mac, dscp=vdscp.IP_API_DSCP_EF) Client.add_vpp_config() rx = self.pg3.get_capture(1) self.verify_orig_dhcp_discover(rx[0], self.pg3, hostname, self.pg3.local_mac, dscp=vdscp.IP_API_DSCP_EF) # TODO: VPP DHCP client should not accept DHCP OFFER message with # the XID (Transaction ID) not matching the XID of the most recent # DHCP DISCOVERY message. # Such DHCP OFFER message must be silently discarded - RFC2131. # Reported in Jira ticket: VPP-99 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, dscp=vdscp.IP_API_DSCP_EF) # # unicast the ack to the offered address # 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, chaddr=mac_pton(self.pg3.local_mac)) / 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, 32)) self.assertTrue(find_route(self, self.pg3.local_ip4, 24)) # # remove the DHCP config # Client.remove_vpp_config() 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 # Client.set_client( self.pg3.sw_if_index, hostname, set_broadcast_flag=False) Client.add_vpp_config() rx = self.pg3.get_capture(1) self.verify_orig_dhcp_discover(rx[0], self.pg3, hostname, broadcast=False) # # 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, chaddr=mac_pton(self.pg3.local_mac)) / 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=False) # # Send an acknowledgment, the lease renewal time is 2 seconds # so we should expect the renew straight after # 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, chaddr=mac_pton(self.pg3.local_mac)) / 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), ('renewal_time', 2), '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)) # # read the DHCP client details from a dump # clients = self.vapi.dhcp_client_dump() self.assertEqual(clients[0].client.sw_if_index, self.pg3.sw_if_index) self.assertEqual(clients[0].lease.sw_if_index, self.pg3.sw_if_index) self.assertEqual(clients[0].client.hostname, hostname) self.assertEqual(clients[0].lease.hostname, hostname) # 0 = DISCOVER, 1 = REQUEST, 2 = BOUND self.assertEqual(clients[0].lease.state, 2) self.assertEqual(clients[0].lease.mask_width, 24) self.assertEqual(str(clients[0].lease.router_address), self.pg3.remote_ip4) self.assertEqual(str(clients[0].lease.host_address), self.pg3.local_ip4) # # wait for the unicasted renewal # the first attempt will be an ARP packet, since we have not yet # responded to VPP's request # self.logger.info(self.vapi.cli("sh dhcp client intfc pg3 verbose")) rx = self.pg3.get_capture(1, timeout=10) self.assertEqual(rx[0][ARP].pdst, self.pg3.remote_ip4) # respond to the arp p_arp = (Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac) / ARP(op="is-at", hwdst=self.pg3.local_mac, hwsrc=self.pg3.remote_mac, pdst=self.pg3.local_ip4, psrc=self.pg3.remote_ip4)) self.pg3.add_stream(p_arp) self.pg_enable_capture(self.pg_interfaces) self.pg_start() # the next packet is the unicasted renewal rx = self.pg3.get_capture(1, timeout=10) self.verify_orig_dhcp_request(rx[0], self.pg3, hostname, self.pg3.local_ip4, l2_bc=False, broadcast=False) # send an ACK with different data from the original offer * self.pg3.generate_remote_hosts(4) 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.remote_hosts[3].ip4, chaddr=mac_pton(self.pg3.local_mac)) / DHCP(options=[('message-type', 'ack'), ('subnet_mask', "255.255.255.0"), ('router', self.pg3.remote_hosts[1].ip4), ('server_id', self.pg3.remote_hosts[2].ip4), ('lease_time', 43200), ('renewal_time', 2), 'end'])) self.pg3.add_stream(p_ack) self.pg_enable_capture(self.pg_interfaces) self.pg_start() # # read the DHCP client details from a dump # clients = self.vapi.dhcp_client_dump() self.assertEqual(clients[0].client.sw_if_index, self.pg3.sw_if_index) self.assertEqual(clients[0].lease.sw_if_index, self.pg3.sw_if_index) self.assertEqual(clients[0].client.hostname, hostname) self.assertEqual(clients[0].lease.hostname, hostname) # 0 = DISCOVER, 1 = REQUEST, 2 = BOUND self.assertEqual(clients[0].lease.state, 2) self.assertEqual(clients[0].lease.mask_width, 24) self.assertEqual(str(clients[0].lease.router_address), self.pg3.remote_hosts[1].ip4) self.assertEqual(str(clients[0].lease.host_address), self.pg3.remote_hosts[3].ip4) # # remove the DHCP config # Client.remove_vpp_config() # # 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)) # # Start the procedure again. Use requested lease time option. # this time wait for the lease to expire and the client to # self-destruct # hostname += "-2" self.pg3.admin_down() self.sleep(1) self.pg3.admin_up() self.pg_enable_capture(self.pg_interfaces) Client.set_client(self.pg3.sw_if_index, hostname) Client.add_vpp_config() rx = self.pg3.get_capture(1) self.verify_orig_dhcp_discover(rx[0], self.pg3, hostname) # # Send back on offer with requested lease time, expect the request # lease_time = 1 p_offer = (Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac) / IP(src=self.pg3.remote_ip4, dst='255.255.255.255') / UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT) / BOOTP(op=1, yiaddr=self.pg3.local_ip4, chaddr=mac_pton(self.pg3.local_mac)) / DHCP(options=[('message-type', 'offer'), ('server_id', self.pg3.remote_ip4), ('lease_time', lease_time), '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) # # Send an acknowledgment # p_ack = (Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac) / IP(src=self.pg3.remote_ip4, dst='255.255.255.255') / UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT) / BOOTP(op=1, yiaddr=self.pg3.local_ip4, chaddr=mac_pton(self.pg3.local_mac)) / DHCP(options=[('message-type', 'ack'), ('subnet_mask', '255.255.255.0'), ('router', self.pg3.remote_ip4), ('server_id', self.pg3.remote_ip4), ('lease_time', lease_time), '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) # # At the end of this procedure there should be a connected route # in the FIB # self.assertTrue(find_route(self, self.pg3.local_ip4, 32)) self.assertTrue(find_route(self, self.pg3.local_ip4, 24)) # # the route should be gone after the lease expires # self.assertTrue(self.wait_for_no_route(self.pg3.local_ip4, 32)) self.assertTrue(self.wait_for_no_route(self.pg3.local_ip4, 24)) # # remove the DHCP config # Client.remove_vpp_config() def test_dhcp_client_vlan(self): """ DHCP Client w/ VLAN""" vdscp = VppEnum.vl_api_ip_dscp_t vqos = VppEnum.vl_api_qos_source_t hostname = 'universal-dp' self.pg_enable_capture(self.pg_interfaces) vlan_100 = VppDot1QSubint(self, self.pg3, 100) vlan_100.admin_up() output = [scapy.compat.chb(4)] * 256 os = b''.join(output) rows = [{'outputs': os}, {'outputs': os}, {'outputs': os}, {'outputs': os}] qem1 = VppQosEgressMap(self, 1, rows).add_vpp_config() qm1 = VppQosMark(self, vlan_100, qem1, vqos.QOS_API_SOURCE_VLAN).add_vpp_config() # # Configure DHCP client on PG3 and capture the discover sent # Client = VppDHCPClient( self, vlan_100.sw_if_index, hostname, dscp=vdscp.IP_API_DSCP_EF) Client.add_vpp_config() rx = self.pg3.get_capture(1) self.assertEqual(rx[0][Dot1Q].vlan, 100) self.assertEqual(rx[0][Dot1Q].prio, 2) self.verify_orig_dhcp_discover(rx[0], self.pg3, hostname, dscp=vdscp.IP_API_DSCP_EF) if __name__ == '__main__': unittest.main(testRunner=VppTestRunner)