summaryrefslogtreecommitdiffstats
path: root/src/vlib/threads.h
blob: c1188cea9338a67422c9a56d0b2c0f38338cc744 (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
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
/*
 * Copyright (c) 2015 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.
 */
#ifndef included_vlib_threads_h
#define included_vlib_threads_h

#include <vlib/main.h>
#include <linux/sched.h>

extern vlib_main_t **vlib_mains;

void vlib_set_thread_name (char *name);

/* arg is actually a vlib__thread_t * */
typedef void (vlib_thread_function_t) (void *arg);

typedef struct vlib_thread_registration_
{
  /* constructor generated list of thread registrations */
  struct vlib_thread_registration_ *next;

  /* config parameters */
  char *name;
  char *short_name;
  vlib_thread_function_t *function;
  uword mheap_size;
  int fixed_count;
  u32 count;
  int no_data_structure_clone;
  u32 frame_queue_nelts;

  /* All threads of this type run on pthreads */
  int use_pthreads;
  u32 first_index;
  uword *coremask;
} vlib_thread_registration_t;

/*
 * Frames have their cpu / vlib_main_t index in the low-order N bits
 * Make VLIB_MAX_CPUS a power-of-two, please...
 */

#ifndef VLIB_MAX_CPUS
#define VLIB_MAX_CPUS 256
#endif

#if VLIB_MAX_CPUS > CLIB_MAX_MHEAPS
#error Please increase number of per-cpu mheaps
#endif

#define VLIB_CPU_MASK (VLIB_MAX_CPUS - 1)	/* 0x3f, max */
#define VLIB_OFFSET_MASK (~VLIB_CPU_MASK)

#define VLIB_LOG2_THREAD_STACK_SIZE (21)
#define VLIB_THREAD_STACK_SIZE (1<<VLIB_LOG2_THREAD_STACK_SIZE)

typedef enum
{
  VLIB_FRAME_QUEUE_ELT_DISPATCH_FRAME,
} vlib_frame_queue_msg_type_t;

typedef struct
{
  CLIB_CACHE_LINE_ALIGN_MARK (cacheline0);
  volatile u32 valid;
  u32 msg_type;
  u32 n_vectors;
  u32 last_n_vectors;

  /* 256 * 4 = 1024 bytes, even mult of cache line size */
  u32 buffer_index[VLIB_FRAME_SIZE];
}
vlib_frame_queue_elt_t;

typedef struct
{
  /* First cache line */
  CLIB_CACHE_LINE_ALIGN_MARK (cacheline0);
  volatile u32 *wait_at_barrier;
  volatile u32 *workers_at_barrier;

  /* Second Cache Line */
    CLIB_CACHE_LINE_ALIGN_MARK (cacheline1);
  void *thread_mheap;
  u8 *thread_stack;
  void (*thread_function) (void *);
  void *thread_function_arg;
  i64 recursion_level;
  elog_track_t elog_track;
  u32 instance_id;
  vlib_thread_registration_t *registration;
  u8 *name;
  u64 barrier_sync_count;
  u8 barrier_elog_enabled;
  const char *barrier_caller;
  const char *barrier_context;
  volatile u32 *node_reforks_required;

  long lwp;
  int cpu_id;
  int core_id;
  int numa_id;
  pthread_t thread_id;
} vlib_worker_thread_t;

extern vlib_worker_thread_t *vlib_worker_threads;

typedef struct
{
  /* enqueue side */
  CLIB_CACHE_LINE_ALIGN_MARK (cacheline0);
  volatile u64 tail;
  u64 enqueues;
  u64 enqueue_ticks;
  u64 enqueue_vectors;
  u32 enqueue_full_events;

  /* dequeue side */
    CLIB_CACHE_LINE_ALIGN_MARK (cacheline1);
  volatile u64 head;
  u64 dequeues;
  u64 dequeue_ticks;
  u64 dequeue_vectors;
  u64 trace;
  u64 vector_threshold;

  /* dequeue hint to enqueue side */
    CLIB_CACHE_LINE_ALIGN_MARK (cacheline2);
  volatile u64 head_hint;

  /* read-only, constant, shared */
    CLIB_CACHE_LINE_ALIGN_MARK (cacheline3);
  vlib_frame_queue_elt_t *elts;
  u32 nelts;
}
vlib_frame_queue_t;

typedef struct
{
  vlib_frame_queue_elt_t **handoff_queue_elt_by_thread_index;
  vlib_frame_queue_t **congested_handoff_queue_by_thread_index;
} vlib_frame_queue_per_thread_data_t;

typedef struct
{
  u32 node_index;
  u32 frame_queue_nelts;
  u32 queue_hi_thresh;

  vlib_frame_queue_t **vlib_frame_queues;
  vlib_frame_queue_per_thread_data_t *per_thread_data;

  /* for frame queue tracing */
  frame_queue_trace_t *frame_queue_traces;
  frame_queue_nelt_counter_t *frame_queue_histogram;
} vlib_frame_queue_main_t;

typedef struct
{
  uword node_index;
  uword type_opaque;
  uword data;
} vlib_process_signal_event_mt_args_t;

/* Called early, in thread 0's context */
clib_error_t *vlib_thread_init (vlib_main_t * vm);

int vlib_frame_queue_enqueue (vlib_main_t * vm, u32 node_runtime_index,
			      u32 frame_queue_index, vlib_frame_t * frame,
			      vlib_frame_queue_msg_type_t type);

int
vlib_frame_queue_dequeue (vlib_main_t * vm, vlib_frame_queue_main_t * fqm);

void vlib_worker_thread_node_runtime_update (void);

void vlib_create_worker_threads (vlib_main_t * vm, int n,
				 void (*thread_function) (void *));

void vlib_worker_thread_init (vlib_worker_thread_t * w);
u32 vlib_frame_queue_main_init (u32 node_index, u32 frame_queue_nelts);

/* Check for a barrier sync request every 30ms */
#define BARRIER_SYNC_DELAY (0.030000)

#if CLIB_DEBUG > 0
/* long barrier timeout, for gdb... */
#define BARRIER_SYNC_TIMEOUT (600.1)
#else
#define BARRIER_SYNC_TIMEOUT (1.0)
#endif

#define vlib_worker_thread_barrier_sync(X) {vlib_worker_thread_barrier_sync_int(X, __FUNCTION__);}

void vlib_worker_thread_barrier_sync_int (vlib_main_t * vm,
					  const char *func_name);
void vlib_worker_thread_barrier_release (vlib_main_t * vm);
void vlib_worker_thread_initial_barrier_sync_and_release (vlib_main_t * vm);
void vlib_worker_thread_node_refork (void);

static_always_inline uword
vlib_get_thread_index (void)
{
  return __os_thread_index;
}

always_inline void
vlib_smp_unsafe_warning (void)
{
  if (CLIB_DEBUG > 0)
    {
      if (vlib_get_thread_index ())
	fformat (stderr, "%s: SMP unsafe warning...\n", __FUNCTION__);
    }
}

typedef enum
{
  VLIB_WORKER_THREAD_FORK_FIXUP_ILLEGAL = 0,
  VLIB_WORKER_THREAD_FORK_FIXUP_NEW_SW_IF_INDEX,
} vlib_fork_fixup_t;

void vlib_worker_thread_fork_fixup (vlib_fork_fixup_t which);

#define foreach_vlib_main(body)                         \
do {                                                    \
  vlib_main_t ** __vlib_mains = 0, *this_vlib_main;     \
  int ii;                                               \
                                                        \
  for (ii = 0; ii < vec_len (vlib_mains); ii++)         \
    {                                                   \
      this_vlib_main = vlib_mains[ii];                  \
      ASSERT (ii == 0 ||                                \
	      this_vlib_main->parked_at_barrier == 1);  \
      if (this_vlib_main)                               \
        vec_add1 (__vlib_mains, this_vlib_main);        \
    }                                                   \
                                                        \
  for (ii = 0; ii < vec_len (__vlib_mains); ii++)       \
    {                                                   \
      this_vlib_main = __vlib_mains[ii];                \
      /* body uses this_vlib_main... */                 \
      (body);                                           \
    }                                                   \
  vec_free (__vlib_mains);                              \
} while (0);

#define foreach_sched_policy \
  _(SCHED_OTHER, OTHER, "other") \
  _(SCHED_BATCH, BATCH, "batch") \
  _(SCHED_IDLE, IDLE, "idle")   \
  _(SCHED_FIFO, FIFO, "fifo")   \
  _(SCHED_RR, RR, "rr")

typedef enum
{
#define _(v,f,s) SCHED_POLICY_##f = v,
  foreach_sched_policy
#undef _
    SCHED_POLICY_N,
} sched_policy_t;

typedef struct
{
  clib_error_t *(*vlib_launch_thread_cb) (void *fp, vlib_worker_thread_t * w,
					  unsigned cpu_id);
  clib_error_t *(*vlib_thread_set_lcore_cb) (u32 thread, u16 cpu);
} vlib_thread_callbacks_t;

typedef struct
{
  /* Link list of registrations, built by constructors */
  vlib_thread_registration_t *next;

  /* Vector of registrations, w/ non-data-structure clones at the top */
  vlib_thread_registration_t **registrations;

  uword *thread_registrations_by_name;

  vlib_worker_thread_t *worker_threads;

  /*
   * Launch all threads as pthreads,
   * not eal_rte_launch (strict affinity) threads
   */
  int use_pthreads;

  /* Number of vlib_main / vnet_main clones */
  u32 n_vlib_mains;

  /* Number of thread stacks to create */
  u32 n_thread_stacks;

  /* Number of pthreads */
  u32 n_pthreads;

  /* Number of threads */
  u32 n_threads;

  /* Number of cores to skip, must match the core mask */
  u32 skip_cores;

  /* Thread prefix name */
  u8 *thread_prefix;

  /* main thread lcore */
  u32 main_lcore;

  /* Bitmap of available CPU cores */
  uword *cpu_core_bitmap;

  /* Bitmap of available CPU sockets (NUMA nodes) */
  uword *cpu_socket_bitmap;

  /* Worker handoff queues */
  vlib_frame_queue_main_t *frame_queue_mains;

  /* worker thread initialization barrier */
  volatile u32 worker_thread_release;

  /* scheduling policy */
  u32 sched_policy;

  /* scheduling policy priority */
  u32 sched_priority;

  /* callbacks */
  vlib_thread_callbacks_t cb;
  int extern_thread_mgmt;

  /* NUMA-bound heap size */
  uword numa_heap_size;

} vlib_thread_main_t;

extern vlib_thread_main_t vlib_thread_main;

#include <vlib/global_funcs.h>

#define VLIB_REGISTER_THREAD(x,...)                     \
  __VA_ARGS__ vlib_thread_registration_t x;             \
static void __vlib_add_thread_registration_##x (void)   \
  __attribute__((__constructor__)) ;                    \
static void __vlib_add_thread_registration_##x (void)   \
{                                                       \
  vlib_thread_main_t * tm = &vlib_thread_main;          \
  x.next = tm->next;                                    \
  tm->next = &x;                                        \
}                                                       \
static void __vlib_rm_thread_registration_##x (void)    \
  __attribute__((__destructor__)) ;                     \
static void __vlib_rm_thread_registration_##x (void)    \
{                                                       \
  vlib_thread_main_t * tm = &vlib_thread_main;          \
  VLIB_REMOVE_FROM_LINKED_LIST (tm->next, &x, next);    \
}                                                       \
__VA_ARGS__ vlib_thread_registration_t x

always_inline u32
vlib_num_workers ()
{
  return vlib_thread_main.n_vlib_mains - 1;
}

always_inline u32
vlib_get_worker_thread_index (u32 worker_index)
{
  return worker_index + 1;
}

always_inline u32
vlib_get_worker_index (u32 thread_index)
{
  return thread_index - 1;
}

always_inline u32
vlib_get_current_worker_index ()
{
  return vlib_get_thread_index () - 1;
}

static inline void
vlib_worker_thread_barrier_check (void)
{
  if (PREDICT_FALSE (*vlib_worker_threads->wait_at_barrier))
    {
      vlib_main_t *vm = vlib_get_main ();
      u32 thread_index = vm->thread_index;
      f64 t = vlib_time_now (vm);

      if (PREDICT_FALSE (vlib_worker_threads->barrier_elog_enabled))
	{
	  vlib_worker_thread_t *w = vlib_worker_threads + thread_index;
	  /* *INDENT-OFF* */
	  ELOG_TYPE_DECLARE (e) = {
	    .format = "barrier-wait-thread-%d",
	    .format_args = "i4",
	  };
	  /* *INDENT-ON* */

	  struct
	  {
	    u32 thread_index;
	  } __clib_packed *ed;

	  ed = ELOG_TRACK_DATA (&vlib_global_main.elog_main, e,
				w->elog_track);
	  ed->thread_index = thread_index;
	}

      if (CLIB_DEBUG > 0)
	{
	  vm = vlib_get_main ();
	  vm->parked_at_barrier = 1;
	}
      clib_atomic_fetch_add (vlib_worker_threads->workers_at_barrier, 1);
      while (*vlib_worker_threads->wait_at_barrier)
	;

      /*
       * Recompute the offset from thread-0 time.
       * Note that vlib_time_now adds vm->time_offset, so
       * clear it first. Save the resulting idea of "now", to
       * see how well we're doing. See show_clock_command_fn(...)
       */
      {
	f64 now;
	vm->time_offset = 0.0;
	now = vlib_time_now (vm);
	vm->time_offset = vlib_global_main.time_last_barrier_release - now;
	vm->time_last_barrier_release = vlib_time_now (vm);
      }

      if (CLIB_DEBUG > 0)
	vm->parked_at_barrier = 0;
      clib_atomic_fetch_add (vlib_worker_threads->workers_at_barrier, -1);

      if (PREDICT_FALSE (*vlib_worker_threads->node_reforks_required))
	{
	  if (PREDICT_FALSE (vlib_worker_threads->barrier_elog_enabled))
	    {
	      t = vlib_time_now (vm) - t;
	      vlib_worker_thread_t *w = vlib_worker_threads + thread_index;
              /* *INDENT-OFF* */
              ELOG_TYPE_DECLARE (e) = {
                .format = "barrier-refork-thread-%d",
                .format_args = "i4",
              };
              /* *INDENT-ON* */

	      struct
	      {
		u32 thread_index;
	      } __clib_packed *ed;

	      ed = ELOG_TRACK_DATA (&vlib_global_main.elog_main, e,
				    w->elog_track);
	      ed->thread_index = thread_index;
	    }

	  vlib_worker_thread_node_refork ();
	  clib_atomic_fetch_add (vlib_worker_threads->node_reforks_required,
				 -1);
	  while (*vlib_worker_threads->node_reforks_required)
	    ;
	}
      if (PREDICT_FALSE (vlib_worker_threads->barrier_elog_enabled))
	{
	  t = vlib_time_now (vm) - t;
	  vlib_worker_thread_t *w = vlib_worker_threads + thread_index;
	  /* *INDENT-OFF* */
	  ELOG_TYPE_DECLARE (e) = {
	    .format = "barrier-released-thread-%d: %dus",
	    .format_args = "i4i4",
	  };
	  /* *INDENT-ON* */

	  struct
	  {
	    u32 thread_index;
	    u32 duration;
	  } __clib_packed *ed;

	  ed = ELOG_TRACK_DATA (&vlib_global_main.elog_main, e,
				w->elog_track);
	  ed->thread_index = thread_index;
	  ed->duration = (int) (1000000.0 * t);
	}
    }
}

always_inline vlib_main_t *
vlib_get_worker_vlib_main (u32 worker_index)
{
  vlib_main_t *vm;
  vlib_thread_main_t *tm = &vlib_thread_main;
  ASSERT (worker_index < tm->n_vlib_mains - 1);
  vm = vlib_mains[worker_index + 1];
  ASSERT (vm);
  return vm;
}

static inline u8
vlib_thread_is_main_w_barrier (void)
{
  return (!vlib_num_workers ()
	  || ((vlib_get_thread_index () == 0
	       && vlib_worker_threads->wait_at_barrier[0])));
}

static inline void
vlib_put_frame_queue_elt (vlib_frame_queue_elt_t * hf)
{
  CLIB_MEMORY_BARRIER ();
  hf->valid = 1;
}

static inline vlib_frame_queue_elt_t *
vlib_get_frame_queue_elt (u32 frame_queue_index, u32 index)
{
  vlib_frame_queue_t *fq;
  vlib_frame_queue_elt_t *elt;
  vlib_thread_main_t *tm = &vlib_thread_main;
  vlib_frame_queue_main_t *fqm =
    vec_elt_at_index (tm->frame_queue_mains, frame_queue_index);
  u64 new_tail;

  fq = fqm->vlib_frame_queues[index];
  ASSERT (fq);

  new_tail = clib_atomic_add_fetch (&fq->tail, 1);

  /* Wait until a ring slot is available */
  while (new_tail >= fq->head_hint + fq->nelts)
    vlib_worker_thread_barrier_check ();

  elt = fq->elts + (new_tail & (fq->nelts - 1));

  /* this would be very bad... */
  while (elt->valid)
    ;

  elt->msg_type = VLIB_FRAME_QUEUE_ELT_DISPATCH_FRAME;
  elt->last_n_vectors = elt->n_vectors = 0;

  return elt;
}

static inline vlib_frame_queue_t *
is_vlib_frame_queue_congested (u32 frame_queue_index,
			       u32 index,
			       u32 queue_hi_thresh,
			       vlib_frame_queue_t **
			       handoff_queue_by_worker_index)
{
  vlib_frame_queue_t *fq;
  vlib_thread_main_t *tm = &vlib_thread_main;
  vlib_frame_queue_main_t *fqm =
    vec_elt_at_index (tm->frame_queue_mains, frame_queue_index);

  fq = handoff_queue_by_worker_index[index];
  if (fq != (vlib_frame_queue_t *) (~0))
    return fq;

  fq = fqm->vlib_frame_queues[index];
  ASSERT (fq);

  if (PREDICT_FALSE (fq->tail >= (fq->head_hint + queue_hi_thresh)))
    {
      /* a valid entry in the array will indicate the queue has reached
       * the specified threshold and is congested
       */
      handoff_queue_by_worker_index[index] = fq;
      fq->enqueue_full_events++;
      return fq;
    }

  return NULL;
}

static inline vlib_frame_queue_elt_t *
vlib_get_worker_handoff_queue_elt (u32 frame_queue_index,
				   u32 vlib_worker_index,
				   vlib_frame_queue_elt_t **
				   handoff_queue_elt_by_worker_index)
{
  vlib_frame_queue_elt_t *elt;

  if (handoff_queue_elt_by_worker_index[vlib_worker_index])
    return handoff_queue_elt_by_worker_index[vlib_worker_index];

  elt = vlib_get_frame_queue_elt (frame_queue_index, vlib_worker_index);

  handoff_queue_elt_by_worker_index[vlib_worker_index] = elt;

  return elt;
}

u8 *vlib_thread_stack_init (uword thread_index);
int vlib_thread_cb_register (struct vlib_main_t *vm,
			     vlib_thread_callbacks_t * cb);
extern void *rpc_call_main_thread_cb_fn;

void
vlib_process_signal_event_mt_helper (vlib_process_signal_event_mt_args_t *
				     args);
void vlib_rpc_call_main_thread (void *function, u8 * args, u32 size);
void 
#!/usr/bin/env python

import unittest
import binascii
from socket import AF_INET6

from framework import VppTestCase, VppTestRunner
from vpp_ip_route import VppIpRoute
, Dot1Q from scapy.layers.inet6 import IPv6, UDP, IPv6ExtHdrSegmentRouting from scapy.layers.inet import IP, UDP from scapy.utils import inet_pton, inet_ntop from util import ppp class TestSRv6(VppTestCase): """ SRv6 Test Case """ @classmethod def setUpClass(self): super(TestSRv6, self).setUpClass() def setUp(self): """ Perform test setup before each test case. """ super(TestSRv6, self).setUp() # packet sizes, inclusive L2 overhead self.pg_packet_sizes = [64, 512, 1518, 9018] # reset packet_infos self.reset_packet_infos() def tearDown(self): """ Clean up test setup after each test case. """ self.teardown_interfaces() super(TestSRv6, self).tearDown() def configure_interface(self, interface, ipv6=False, ipv4=False, ipv6_table_id=0, ipv4_table_id=0): """ Configure interface. :param ipv6: configure IPv6 on interface :param ipv4: configure IPv4 on interface :param ipv6_table_id: FIB table_id for IPv6 :param ipv4_table_id: FIB table_id for IPv4 """ self.logger.debug("Configuring interface %s" % (interface.name)) if ipv6: self.logger.debug("Configuring IPv6") interface.set_table_ip6(ipv6_table_id) interface.config_ip6() interface.resolve_ndp(timeout=5) if ipv4: self.logger.debug("Configuring IPv4") interface.set_table_ip4(ipv4_table_id) interface.config_ip4() interface.resolve_arp() interface.admin_up() def setup_interfaces(self, ipv6=[], ipv4=[], ipv6_table_id=[], ipv4_table_id=[]): """ Create and configure interfaces. :param ipv6: list of interface IPv6 capabilities :param ipv4: list of interface IPv4 capabilities :param ipv6_table_id: list of intf IPv6 FIB table_ids :param ipv4_table_id: list of intf IPv4 FIB table_ids :returns: List of created interfaces. """ # how many interfaces? if len(ipv6): count = len(ipv6) else: count = len(ipv4) self.logger.debug("Creating and configuring %d interfaces" % (count)) # fill up ipv6 and ipv4 lists if needed # not enabled (False) is the default if len(ipv6) < count: ipv6 += (count - len(ipv6)) * [False] if len(ipv4) < count: ipv4 += (count - len(ipv4)) * [False] # fill up table_id lists if needed # table_id 0 (global) is the default if len(ipv6_table_id) < count: ipv6_table_id += (count - len(ipv6_table_id)) * [0] if len(ipv4_table_id) < count: ipv4_table_id += (count - len(ipv4_table_id)) * [0] # create 'count' pg interfaces self.create_pg_interfaces(range(count)) # setup all interfaces for i in range(count): intf = self.pg_interfaces[i] self.configure_interface(intf, ipv6[i], ipv4[i], ipv6_table_id[i], ipv4_table_id[i]) if any(ipv6): self.logger.debug(self.vapi.cli("show ip6 neighbors")) if any(ipv4): self.logger.debug(self.vapi.cli("show ip arp")) self.logger.debug(self.vapi.cli("show interface")) self.logger.debug(self.vapi.cli("show hardware")) return self.pg_interfaces def teardown_interfaces(self): """ Unconfigure and bring down interface. """ self.logger.debug("Tearing down interfaces") # tear down all interfaces # AFAIK they cannot be deleted for i in self.pg_interfaces: self.logger.debug("Tear down interface %s" % (i.name)) i.admin_down() i.unconfig() i.set_table_ip4(0) i.set_table_ip6(0) @unittest.skipUnless(0, "PC to fix") def test_SRv6_T_Encaps(self): """ Test SRv6 Transit.Encaps behavior for IPv6. """ # send traffic to one destination interface # source and destination are IPv6 only self.setup_interfaces(ipv6=[True, True]) # configure FIB entries route = VppIpRoute(self, "a4::", 64, [VppRoutePath(self.pg1.remote_ip6, self.pg1.sw_if_index, proto=DpoProto.DPO_PROTO_IP6)], is_ip6=1) route.add_vpp_config() # configure encaps IPv6 source address # needs to be done before SR Policy config # TODO: API? self.vapi.cli("set sr encaps source addr a3::") bsid = 'a3::9999:1' # configure SRv6 Policy # Note: segment list order: first -> last sr_policy = VppSRv6Policy( self, bsid=bsid, is_encap=1, sr_type=SRv6PolicyType.SR_POLICY_TYPE_DEFAULT, weight=1, fib_table=0, segments=['a4::', 'a5::', 'a6::c7'], source='a3::') sr_policy.add_vpp_config() self.sr_policy = sr_policy # log the sr policies self.logger.info(self.vapi.cli("show sr policies")) # steer IPv6 traffic to a7::/64 into SRv6 Policy # use the bsid of the above self.sr_policy pol_steering = VppSRv6Steering( self, bsid=self.sr_policy.bsid, prefix="a7::", mask_width=64, traffic_type=SRv6PolicySteeringTypes.SR_STEER_IPV6, sr_policy_index=0, table_id=0, sw_if_index=0) pol_steering.add_vpp_config() # log the sr steering policies self.logger.info(self.vapi.cli("show sr steering policies")) # create packets count = len(self.pg_packet_sizes) dst_inner = 'a7::1234' pkts = [] # create IPv6 packets without SRH packet_header = self.create_packet_header_IPv6(dst_inner) # create traffic stream pg0->pg1 pkts.extend(self.create_stream(self.pg0, self.pg1, packet_header, self.pg_packet_sizes, count)) # create IPv6 packets with SRH # packets with segments-left 1, active segment a7:: packet_header = self.create_packet_header_IPv6_SRH( sidlist=['a8::', 'a7::', 'a6::'], segleft=1) # create traffic stream pg0->pg1 pkts.extend(self.create_stream(self.pg0, self.pg1, packet_header, self.pg_packet_sizes, count)) # create IPv6 packets with SRH and IPv6 # packets with segments-left 1, active segment a7:: packet_header = self.create_packet_header_IPv6_SRH_IPv6( dst_inner, sidlist=['a8::', 'a7::', 'a6::'], segleft=1) # create traffic stream pg0->pg1 pkts.extend(self.create_stream(self.pg0, self.pg1, packet_header, self.pg_packet_sizes, count)) # send packets and verify received packets self.send_and_verify_pkts(self.pg0, pkts, self.pg1, self.compare_rx_tx_packet_T_Encaps) # log the localsid counters self.logger.info(self.vapi.cli("show sr localsid")) # remove SR steering pol_steering.remove_vpp_config() self.logger.info(self.vapi.cli("show sr steering policies")) # remove SR Policies self.sr_policy.remove_vpp_config() self.logger.info(self.vapi.cli("show sr policies")) # remove FIB entries # done by tearDown # cleanup interfaces self.teardown_interfaces() @unittest.skipUnless(0, "PC to fix") def test_SRv6_T_Insert(self): """ Test SRv6 Transit.Insert behavior (IPv6 only). """ # send traffic to one destination interface # source and destination are IPv6 only self.setup_interfaces(ipv6=[True, True]) # configure FIB entries route = VppIpRoute(self, "a4::", 64, [VppRoutePath(self.pg1.remote_ip6, self.pg1.sw_if_index, proto=DpoProto.DPO_PROTO_IP6)], is_ip6=1) route.add_vpp_config() # configure encaps IPv6 source address # needs to be done before SR Policy config # TODO: API? self.vapi.cli("set sr encaps source addr a3::") bsid = 'a3::9999:1' # configure SRv6 Policy # Note: segment list order: first -> last sr_policy = VppSRv6Policy( self, bsid=bsid, is_encap=0, sr_type=SRv6PolicyType.SR_POLICY_TYPE_DEFAULT, weight=1, fib_table=0, segments=['a4::', 'a5::', 'a6::c7'], source='a3::') sr_policy.add_vpp_config() self.sr_policy = sr_policy # log the sr policies self.logger.info(self.vapi.cli("show sr policies")) # steer IPv6 traffic to a7::/64 into SRv6 Policy # use the bsid of the above self.sr_policy pol_steering = VppSRv6Steering( self, bsid=self.sr_policy.bsid, prefix="a7::", mask_width=64, traffic_type=SRv6PolicySteeringTypes.SR_STEER_IPV6, sr_policy_index=0, table_id=0, sw_if_index=0) pol_steering.add_vpp_config() # log the sr steering policies self.logger.info(self.vapi.cli("show sr steering policies")) # create packets count = len(self.pg_packet_sizes) dst_inner = 'a7::1234' pkts = [] # create IPv6 packets without SRH packet_header = self.create_packet_header_IPv6(dst_inner) # create traffic stream pg0->pg1 pkts.extend(self.create_stream(self.pg0, self.pg1, packet_header, self.pg_packet_sizes, count)) # create IPv6 packets with SRH # packets with segments-left 1, active segment a7:: packet_header = self.create_packet_header_IPv6_SRH( sidlist=['a8::', 'a7::', 'a6::'], segleft=1) # create traffic stream pg0->pg1 pkts.extend(self.create_stream(self.pg0, self.pg1, packet_header, self.pg_packet_sizes, count)) # send packets and verify received packets self.send_and_verify_pkts(self.pg0, pkts, self.pg1, self.compare_rx_tx_packet_T_Insert) # log the localsid counters self.logger.info(self.vapi.cli("show sr localsid")) # remove SR steering pol_steering.remove_vpp_config() self.logger.info(self.vapi.cli("show sr steering policies")) # remove SR Policies self.sr_policy.remove_vpp_config() self.logger.info(self.vapi.cli("show sr policies")) # remove FIB entries # done by tearDown # cleanup interfaces self.teardown_interfaces() @unittest.skipUnless(0, "PC to fix") def test_SRv6_T_Encaps_IPv4(self): """ Test SRv6 Transit.Encaps behavior for IPv4. """ # send traffic to one destination interface # source interface is IPv4 only # destination interface is IPv6 only self.setup_interfaces(ipv6=[False, True], ipv4=[True, False]) # configure FIB entries route = VppIpRoute(self, "a4::", 64, [VppRoutePath(self.pg1.remote_ip6, self.pg1.sw_if_index, proto=DpoProto.DPO_PROTO_IP6)], is_ip6=1) route.add_vpp_config() # configure encaps IPv6 source address # needs to be done before SR Policy config # TODO: API? self.vapi.cli("set sr encaps source addr a3::") bsid = 'a3::9999:1' # configure SRv6 Policy # Note: segment list order: first -> last sr_policy = VppSRv6Policy( self, bsid=bsid, is_encap=1, sr_type=SRv6PolicyType.SR_POLICY_TYPE_DEFAULT, weight=1, fib_table=0, segments=['a4::', 'a5::', 'a6::c7'], source='a3::') sr_policy.add_vpp_config() self.sr_policy = sr_policy # log the sr policies self.logger.info(self.vapi.cli("show sr policies")) # steer IPv4 traffic to 7.1.1.0/24 into SRv6 Policy # use the bsid of the above self.sr_policy pol_steering = VppSRv6Steering( self, bsid=self.sr_policy.bsid, prefix="7.1.1.0", mask_width=24, traffic_type=SRv6PolicySteeringTypes.SR_STEER_IPV4, sr_policy_index=0, table_id=0, sw_if_index=0) pol_steering.add_vpp_config() # log the sr steering policies self.logger.info(self.vapi.cli("show sr steering policies")) # create packets count = len(self.pg_packet_sizes) dst_inner = '7.1.1.123' pkts = [] # create IPv4 packets packet_header = self.create_packet_header_IPv4(dst_inner) # create traffic stream pg0->pg1 pkts.extend(self.create_stream(self.pg0, self.pg1, packet_header, self.pg_packet_sizes, count)) # send packets and verify received packets self.send_and_verify_pkts(self.pg0, pkts, self.pg1, self.compare_rx_tx_packet_T_Encaps_IPv4) # log the localsid counters self.logger.info(self.vapi.cli("show sr localsid")) # remove SR steering pol_steering.remove_vpp_config() self.logger.info(self.vapi.cli("show sr steering policies")) # remove SR Policies self.sr_policy.remove_vpp_config() self.logger.info(self.vapi.cli("show sr policies")) # remove FIB entries # done by tearDown # cleanup interfaces self.teardown_interfaces() @unittest.skip("VPP crashes after running this test") def test_SRv6_T_Encaps_L2(self): """ Test SRv6 Transit.Encaps behavior for L2. """ # send traffic to one destination interface # source interface is IPv4 only TODO? # destination interface is IPv6 only self.setup_interfaces(ipv6=[False, True], ipv4=[False, False]) # configure FIB entries route = VppIpRoute(self, "a4::", 64, [VppRoutePath(self.pg1.remote_ip6, self.pg1.sw_if_index, proto=DpoProto.DPO_PROTO_IP6)], is_ip6=1) route.add_vpp_config() # configure encaps IPv6 source address # needs to be done before SR Policy config # TODO: API? self.vapi.cli("set sr encaps source addr a3::") bsid = 'a3::9999:1' # configure SRv6 Policy # Note: segment list order: first -> last sr_policy = VppSRv6Policy( self, bsid=bsid, is_encap=1, sr_type=SRv6PolicyType.SR_POLICY_TYPE_DEFAULT, weight=1, fib_table=0, segments=['a4::', 'a5::', 'a6::c7'], source='a3::') sr_policy.add_vpp_config() self.sr_policy = sr_policy # log the sr policies self.logger.info(self.vapi.cli("show sr policies")) # steer L2 traffic into SRv6 Policy # use the bsid of the above self.sr_policy pol_steering = VppSRv6Steering( self, bsid=self.sr_policy.bsid, prefix="::", mask_width=0, traffic_type=SRv6PolicySteeringTypes.SR_STEER_L2, sr_policy_index=0, table_id=0, sw_if_index=self.pg0.sw_if_index) pol_steering.add_vpp_config() # log the sr steering policies self.logger.info(self.vapi.cli("show sr steering policies")) # create packets count = len(self.pg_packet_sizes) pkts = [] # create L2 packets without dot1q header packet_header = self.create_packet_header_L2() # create traffic stream pg0->pg1 pkts.extend(self.create_stream(self.pg0, self.pg1, packet_header, self.pg_packet_sizes, count)) # create L2 packets with dot1q header packet_header = self.create_packet_header_L2(vlan=123) # create traffic stream pg0->pg1 pkts.extend(self.create_stream(self.pg0, self.pg1, packet_header, self.pg_packet_sizes, count)) # send packets and verify received packets self.send_and_verify_pkts(self.pg0, pkts, self.pg1, self.compare_rx_tx_packet_T_Encaps_L2) # log the localsid counters self.logger.info(self.vapi.cli("show sr localsid")) # remove SR steering pol_steering.remove_vpp_config() self.logger.info(self.vapi.cli("show sr steering policies")) # remove SR Policies self.sr_policy.remove_vpp_config() self.logger.info(self.vapi.cli("show sr policies")) # remove FIB entries # done by tearDown # cleanup interfaces self.teardown_interfaces() def test_SRv6_End(self): """ Test SRv6 End (without PSP) behavior. """ # send traffic to one destination interface # source and destination interfaces are IPv6 only self.setup_interfaces(ipv6=[True, True]) # configure FIB entries route = VppIpRoute(self, "a4::", 64, [VppRoutePath(self.pg1.remote_ip6, self.pg1.sw_if_index, proto=DpoProto.DPO_PROTO_IP6)], is_ip6=1) route.add_vpp_config() # configure SRv6 localSID End without PSP behavior localsid = VppSRv6LocalSID( self, localsid={'addr': 'A3::0'}, behavior=SRv6LocalSIDBehaviors.SR_BEHAVIOR_END, nh_addr4='0.0.0.0', nh_addr6='::', end_psp=0, sw_if_index=0, vlan_index=0, fib_table=0) localsid.add_vpp_config() # log the localsids self.logger.debug(self.vapi.cli("show sr localsid")) # create IPv6 packets with SRH (SL=2, SL=1, SL=0) # send one packet per SL value per packet size # SL=0 packet with localSID End with USP needs 2nd SRH count = len(self.pg_packet_sizes) dst_inner = 'a4::1234' pkts = [] # packets with segments-left 2, active segment a3:: packet_header = self.create_packet_header_IPv6_SRH_IPv6( dst_inner, sidlist=['a5::', 'a4::', 'a3::'], segleft=2) # create traffic stream pg0->pg1 pkts.extend(self.create_stream(self.pg0, self.pg1, packet_header, self.pg_packet_sizes, count)) # packets with segments-left 1, active segment a3:: packet_header = self.create_packet_header_IPv6_SRH_IPv6( dst_inner, sidlist=['a4::', 'a3::', 'a2::'], segleft=1) # add to traffic stream pg0->pg1 pkts.extend(self.create_stream(self.pg0, self.pg1, packet_header, self.pg_packet_sizes, count)) # TODO: test behavior with SL=0 packet (needs 2*SRH?) # send packets and verify received packets self.send_and_verify_pkts(self.pg0, pkts, self.pg1, self.compare_rx_tx_packet_End) # log the localsid counters self.logger.info(self.vapi.cli("show sr localsid")) # remove SRv6 localSIDs localsid.remove_vpp_config() # remove FIB entries # done by tearDown # cleanup interfaces self.teardown_interfaces() def test_SRv6_End_with_PSP(self): """ Test SRv6 End with PSP behavior. """ # send traffic to one destination interface # source and destination interfaces are IPv6 only self.setup_interfaces(ipv6=[True, True]) # configure FIB entries route = VppIpRoute(self, "a4::", 64, [VppRoutePath(self.pg1.remote_ip6, self.pg1.sw_if_index, proto=DpoProto.DPO_PROTO_IP6)], is_ip6=1) route.add_vpp_config() # configure SRv6 localSID End with PSP behavior localsid = VppSRv6LocalSID( self, localsid={'addr': 'A3::0'}, behavior=SRv6LocalSIDBehaviors.SR_BEHAVIOR_END, nh_addr4='0.0.0.0', nh_addr6='::', end_psp=1, sw_if_index=0, vlan_index=0, fib_table=0) localsid.add_vpp_config() # log the localsids self.logger.debug(self.vapi.cli("show sr localsid")) # create IPv6 packets with SRH (SL=2, SL=1) # send one packet per SL value per packet size # SL=0 packet with localSID End with PSP is dropped count = len(self.pg_packet_sizes) dst_inner = 'a4::1234' pkts = [] # packets with segments-left 2, active segment a3:: packet_header = self.create_packet_header_IPv6_SRH_IPv6( dst_inner, sidlist=['a5::', 'a4::', 'a3::'], segleft=2) # create traffic stream pg0->pg1 pkts.extend(self.create_stream(self.pg0, self.pg1, packet_header, self.pg_packet_sizes, count)) # packets with segments-left 1, active segment a3:: packet_header = self.create_packet_header_IPv6_SRH_IPv6( dst_inner, sidlist=['a4::', 'a3::', 'a2::'], segleft=1) # add to traffic stream pg0->pg1 pkts.extend(self.create_stream(self.pg0, self.pg1, packet_header, self.pg_packet_sizes, count)) # send packets and verify received packets self.send_and_verify_pkts(self.pg0, pkts, self.pg1, self.compare_rx_tx_packet_End_PSP) # log the localsid counters self.logger.info(self.vapi.cli("show sr localsid")) # remove SRv6 localSIDs localsid.remove_vpp_config() # remove FIB entries # done by tearDown # cleanup interfaces self.teardown_interfaces() def test_SRv6_End_X(self): """ Test SRv6 End.X (without PSP) behavior. """ # create three interfaces (1 source, 2 destinations) # source and destination interfaces are IPv6 only self.setup_interfaces(ipv6=[True, True, True]) # configure FIB entries # a4::/64 via pg1 and pg2 route = VppIpRoute(self, "a4::", 64, [VppRoutePath(self.pg1.remote_ip6, self.pg1.sw_if_index, proto=DpoProto.DPO_PROTO_IP6), VppRoutePath(self.pg2.remote_ip6, self.pg2.sw_if_index, proto=DpoProto.DPO_PROTO_IP6)], is_ip6=1) route.add_vpp_config() self.logger.debug(self.vapi.cli("show ip6 fib")) # configure SRv6 localSID End.X without PSP behavior # End.X points to interface pg1 localsid = VppSRv6LocalSID( self, localsid={'addr': 'A3::C4'}, behavior=SRv6LocalSIDBehaviors.SR_BEHAVIOR_X, nh_addr4='0.0.0.0', nh_addr6=self.pg1.remote_ip6, end_psp=0, sw_if_index=self.pg1.sw_if_index, vlan_index=0, fib_table=0) localsid.add_vpp_config() # log the localsids self.logger.debug(self.vapi.cli("show sr localsid")) # create IPv6 packets with SRH (SL=2, SL=1) # send one packet per SL value per packet size # SL=0 packet with localSID End with PSP is dropped count = len(self.pg_packet_sizes) dst_inner = 'a4::1234' pkts = [] # packets with segments-left 2, active segment a3::c4 packet_header = self.create_packet_header_IPv6_SRH_IPv6( dst_inner, sidlist=['a5::', 'a4::', 'a3::c4'], segleft=2) # create traffic stream pg0->pg1 pkts.extend(self.create_stream(self.pg0, self.pg1, packet_header, self.pg_packet_sizes, count)) # packets with segments-left 1, active segment a3::c4 packet_header = self.create_packet_header_IPv6_SRH_IPv6( dst_inner, sidlist=['a4::', 'a3::c4', 'a2::'], segleft=1) # add to traffic stream pg0->pg1 pkts.extend(self.create_stream(self.pg0, self.pg1, packet_header, self.pg_packet_sizes, count)) # send packets and verify received packets # using same comparison function as End (no PSP) self.send_and_verify_pkts(self.pg0, pkts, self.pg1, self.compare_rx_tx_packet_End) # assert nothing was received on the other interface (pg2) self.pg2.assert_nothing_captured("mis-directed packet(s)") # log the localsid counters self.logger.info(self.vapi.cli("show sr localsid")) # remove SRv6 localSIDs localsid.remove_vpp_config() # remove FIB entries # done by tearDown # cleanup interfaces self.teardown_interfaces() def test_SRv6_End_X_with_PSP(self): """ Test SRv6 End.X with PSP behavior. """ # create three interfaces (1 source, 2 destinations) # source and destination interfaces are IPv6 only self.setup_interfaces(ipv6=[True, True, True]) # configure FIB entries # a4::/64 via pg1 and pg2 route = VppIpRoute(self, "a4::", 64, [VppRoutePath(self.pg1.remote_ip6, self.pg1.sw_if_index, proto=DpoProto.DPO_PROTO_IP6), VppRoutePath(self.pg2.remote_ip6, self.pg2.sw_if_index, proto=DpoProto.DPO_PROTO_IP6)], is_ip6=1) route.add_vpp_config() # configure SRv6 localSID End with PSP behavior localsid = VppSRv6LocalSID( self, localsid={'addr': 'A3::C4'}, behavior=SRv6LocalSIDBehaviors.SR_BEHAVIOR_X, nh_addr4='0.0.0.0', nh_addr6=self.pg1.remote_ip6, end_psp=1, sw_if_index=self.pg1.sw_if_index, vlan_index=0, fib_table=0) localsid.add_vpp_config() # log the localsids self.logger.debug(self.vapi.cli("show sr localsid")) # create IPv6 packets with SRH (SL=2, SL=1) # send one packet per SL value per packet size # SL=0 packet with localSID End with PSP is dropped count = len(self.pg_packet_sizes) dst_inner = 'a4::1234' pkts = [] # packets with segments-left 2, active segment a3:: packet_header = self.create_packet_header_IPv6_SRH_IPv6( dst_inner, sidlist=['a5::', 'a4::', 'a3::c4'], segleft=2) # create traffic stream pg0->pg1 pkts.extend(self.create_stream(self.pg0, self.pg1, packet_header, self.pg_packet_sizes, count)) # packets with segments-left 1, active segment a3:: packet_header = self.create_packet_header_IPv6_SRH_IPv6( dst_inner, sidlist=['a4::', 'a3::c4', 'a2::'], segleft=1) # add to traffic stream pg0->pg1 pkts.extend(self.create_stream(self.pg0, self.pg1, packet_header, self.pg_packet_sizes, count)) # send packets and verify received packets # using same comparison function as End with PSP self.send_and_verify_pkts(self.pg0, pkts, self.pg1, self.compare_rx_tx_packet_End_PSP) # assert nothing was received on the other interface (pg2) self.pg2.assert_nothing_captured("mis-directed packet(s)") # log the localsid counters self.logger.info(self.vapi.cli("show sr localsid")) # remove SRv6 localSIDs localsid.remove_vpp_config() # remove FIB entries # done by tearDown # cleanup interfaces self.teardown_interfaces() def test_SRv6_End_DX6(self): """ Test SRv6 End.DX6 behavior. """ # send traffic to one destination interface # source and destination interfaces are IPv6 only self.setup_interfaces(ipv6=[True, True]) # configure SRv6 localSID End.DX6 behavior localsid = VppSRv6LocalSID( self, localsid={'addr': 'A3::C4'}, behavior=SRv6LocalSIDBehaviors.SR_BEHAVIOR_DX6, nh_addr4='0.0.0.0', nh_addr6=self.pg1.remote_ip6, end_psp=0, sw_if_index=self.pg1.sw_if_index, vlan_index=0, fib_table=0) localsid.add_vpp_config() # log the localsids self.logger.debug(self.vapi.cli("show sr localsid")) # create IPv6 packets with SRH (SL=0) # send one packet per packet size count = len(self.pg_packet_sizes) dst_inner = 'a4::1234' # inner header destination address pkts = [] # packets with SRH, segments-left 0, active segment a3::c4 packet_header = self.create_packet_header_IPv6_SRH_IPv6( dst_inner, sidlist=['a3::c4', 'a2::', 'a1::'], segleft=0) # add to traffic stream pg0->pg1 pkts.extend(self.create_stream(self.pg0, self.pg1, packet_header, self.pg_packet_sizes, count)) # packets without SRH, IPv6 in IPv6 # outer IPv6 dest addr is the localsid End.DX6 packet_header = self.create_packet_header_IPv6_IPv6( dst_inner, dst_outer='a3::c4') # add to traffic stream pg0->pg1 pkts.extend(self.create_stream(self.pg0, self.pg1, packet_header, self.pg_packet_sizes, count)) # send packets and verify received packets self.send_and_verify_pkts(self.pg0, pkts, self.pg1, self.compare_rx_tx_packet_End_DX6) # log the localsid counters self.logger.info(self.vapi.cli("show sr localsid")) # remove SRv6 localSIDs localsid.remove_vpp_config() # cleanup interfaces self.teardown_interfaces() def test_SRv6_End_DT6(self): """ Test SRv6 End.DT6 behavior. """ # create three interfaces (1 source, 2 destinations) # all interfaces are IPv6 only # source interface in global FIB (0) # destination interfaces in global and vrf vrf_1 = 1 ipt = VppIpTable(self, vrf_1, is_ip6=True) ipt.add_vpp_config() self.setup_interfaces(ipv6=[True, True, True], ipv6_table_id=[0, 0, vrf_1]) # configure FIB entries # a4::/64 is reachable # via pg1 in table 0 (global) # and via pg2 in table vrf_1 route0 = VppIpRoute(self, "a4::", 64, [VppRoutePath(self.pg1.remote_ip6, self.pg1.sw_if_index, proto=DpoProto.DPO_PROTO_IP6, nh_table_id=0)], table_id=0, is_ip6=1) route0.add_vpp_config() route1 = VppIpRoute(self, "a4::", 64, [VppRoutePath(self.pg2.remote_ip6, self.pg2.sw_if_index, proto=DpoProto.DPO_PROTO_IP6, nh_table_id=vrf_1)], table_id=vrf_1, is_ip6=1) route1.add_vpp_config() self.logger.debug(self.vapi.cli("show ip6 fib")) # configure SRv6 localSID End.DT6 behavior # Note: # fib_table: where the localsid is installed # sw_if_index: in T-variants of localsid this is the vrf table_id localsid = VppSRv6LocalSID( self, localsid={'addr': 'A3::C4'}, behavior=SRv6LocalSIDBehaviors.SR_BEHAVIOR_DT6, nh_addr4='0.0.0.0', nh_addr6='::', end_psp=0, sw_if_index=vrf_1, vlan_index=0, fib_table=0) localsid.add_vpp_config() # log the localsids self.logger.debug(self.vapi.cli("show sr localsid")) # create IPv6 packets with SRH (SL=0) # send one packet per packet size count = len(self.pg_packet_sizes) dst_inner = 'a4::1234' # inner header destination address pkts = [] # packets with SRH, segments-left 0, active segment a3::c4 packet_header = self.create_packet_header_IPv6_SRH_IPv6( dst_inner, sidlist=['a3::c4', 'a2::', 'a1::'], segleft=0) # add to traffic stream pg0->pg1 pkts.extend(self.create_stream(self.pg0, self.pg2, packet_header, self.pg_packet_sizes, count)) # packets without SRH, IPv6 in IPv6 # outer IPv6 dest addr is the localsid End.DT6 packet_header = self.create_packet_header_IPv6_IPv6( dst_inner, dst_outer='a3::c4') # add to traffic stream pg0->pg1 pkts.extend(self.create_stream(self.pg0, self.pg2, packet_header, self.pg_packet_sizes, count)) # send packets and verify received packets # using same comparison function as End.DX6 self.send_and_verify_pkts(self.pg0, pkts, self.pg2, self.compare_rx_tx_packet_End_DX6) # assert nothing was received on the other interface (pg2) self.pg1.assert_nothing_captured("mis-directed packet(s)") # log the localsid counters self.logger.info(self.vapi.cli("show sr localsid")) # remove SRv6 localSIDs localsid.remove_vpp_config() # remove FIB entries # done by tearDown # cleanup interfaces self.teardown_interfaces() def test_SRv6_End_DX4(self): """ Test SRv6 End.DX4 behavior. """ # send traffic to one destination interface # source interface is IPv6 only # destination interface is IPv4 only self.setup_interfaces(ipv6=[True, False], ipv4=[False, True]) # configure SRv6 localSID End.DX4 behavior localsid = VppSRv6LocalSID( self, localsid={'addr': 'A3::C4'}, behavior=SRv6LocalSIDBehaviors.SR_BEHAVIOR_DX4, nh_addr4=self.pg1.remote_ip4, nh_addr6='::', end_psp=0, sw_if_index=self.pg1.sw_if_index, vlan_index=0, fib_table=0) localsid.add_vpp_config() # log the localsids self.logger.debug(self.vapi.cli("show sr localsid")) # send one packet per packet size count = len(self.pg_packet_sizes) dst_inner = '4.1.1.123' # inner header destination address pkts = [] # packets with SRH, segments-left 0, active segment a3::c4 packet_header = self.create_packet_header_IPv6_SRH_IPv4( dst_inner, sidlist=['a3::c4', 'a2::', 'a1::'], segleft=0) # add to traffic stream pg0->pg1 pkts.extend(self.create_stream(self.pg0, self.pg1, packet_header, self.pg_packet_sizes, count)) # packets without SRH, IPv4 in IPv6 # outer IPv6 dest addr is the localsid End.DX4 packet_header = self.create_packet_header_IPv6_IPv4( dst_inner, dst_outer='a3::c4') # add to traffic stream pg0->pg1 pkts.extend(self.create_stream(self.pg0, self.pg1, packet_header, self.pg_packet_sizes, count)) # send packets and verify received packets self.send_and_verify_pkts(self.pg0, pkts, self.pg1, self.compare_rx_tx_packet_End_DX4) # log the localsid counters self.logger.info(self.vapi.cli("show sr localsid")) # remove SRv6 localSIDs localsid.remove_vpp_config() # cleanup interfaces self.teardown_interfaces() def test_SRv6_End_DT4(self): """ Test SRv6 End.DT4 behavior. """ # create three interfaces (1 source, 2 destinations) # source interface is IPv6-only # destination interfaces are IPv4 only # source interface in global FIB (0) # destination interfaces in global and vrf vrf_1 = 1 ipt = VppIpTable(self, vrf_1) ipt.add_vpp_config() self.setup_interfaces(ipv6=[True, False, False], ipv4=[False, True, True], ipv6_table_id=[0, 0, 0], ipv4_table_id=[0, 0, vrf_1]) # configure FIB entries # 4.1.1.0/24 is reachable # via pg1 in table 0 (global) # and via pg2 in table vrf_1 route0 = VppIpRoute(self, "4.1.1.0", 24, [VppRoutePath(self.pg1.remote_ip4, self.pg1.sw_if_index, nh_table_id=0)], table_id=0, is_ip6=0) route0.add_vpp_config() route1 = VppIpRoute(self, "4.1.1.0", 24, [VppRoutePath(self.pg2.remote_ip4, self.pg2.sw_if_index, nh_table_id=vrf_1)], table_id=vrf_1, is_ip6=0) route1.add_vpp_config() self.logger.debug(self.vapi.cli("show ip fib")) # configure SRv6 localSID End.DT6 behavior # Note: # fib_table: where the localsid is installed # sw_if_index: in T-variants of localsid: vrf table_id localsid = VppSRv6LocalSID( self, localsid={'addr': 'A3::C4'}, behavior=SRv6LocalSIDBehaviors.SR_BEHAVIOR_DT4, nh_addr4='0.0.0.0', nh_addr6='::', end_psp=0, sw_if_index=vrf_1, vlan_index=0, fib_table=0) localsid.add_vpp_config() # log the localsids self.logger.debug(self.vapi.cli("show sr localsid")) # create IPv6 packets with SRH (SL=0) # send one packet per packet size count = len(self.pg_packet_sizes) dst_inner = '4.1.1.123' # inner header destination address pkts = [] # packets with SRH, segments-left 0, active segment a3::c4 packet_header = self.create_packet_header_IPv6_SRH_IPv4( dst_inner, sidlist=['a3::c4', 'a2::', 'a1::'], segleft=0) # add to traffic stream pg0->pg1 pkts.extend(self.create_stream(self.pg0, self.pg2, packet_header, self.pg_packet_sizes, count)) # packets without SRH, IPv6 in IPv6 # outer IPv6 dest addr is the localsid End.DX4 packet_header = self.create_packet_header_IPv6_IPv4( dst_inner, dst_outer='a3::c4') # add to traffic stream pg0->pg1 pkts.extend(self.create_stream(self.pg0, self.pg2, packet_header, self.pg_packet_sizes, count)) # send packets and verify received packets # using same comparison function as End.DX4 self.send_and_verify_pkts(self.pg0, pkts, self.pg2, self.compare_rx_tx_packet_End_DX4) # assert nothing was received on the other interface (pg2) self.pg1.assert_nothing_captured("mis-directed packet(s)") # log the localsid counters self.logger.info(self.vapi.cli("show sr localsid")) # remove SRv6 localSIDs localsid.remove_vpp_config() # remove FIB entries # done by tearDown # cleanup interfaces self.teardown_interfaces() def test_SRv6_End_DX2(self): """ Test SRv6 End.DX2 behavior. """ # send traffic to one destination interface # source interface is IPv6 only self.setup_interfaces(ipv6=[True, False], ipv4=[False, False]) # configure SRv6 localSID End.DX2 behavior localsid = VppSRv6LocalSID( self, localsid={'addr': 'A3::C4'}, behavior=SRv6LocalSIDBehaviors.SR_BEHAVIOR_DX2, nh_addr4='0.0.0.0', nh_addr6='::', end_psp=0, sw_if_index=self.pg1.sw_if_index, vlan_index=0, fib_table=0) localsid.add_vpp_config() # log the localsids self.logger.debug(self.vapi.cli("show sr localsid")) # send one packet per packet size count = len(self.pg_packet_sizes) pkts = [] # packets with SRH, segments-left 0, active segment a3::c4 # L2 has no dot1q header packet_header = self.create_packet_header_IPv6_SRH_L2( sidlist=['a3::c4', 'a2::', 'a1::'], segleft=0, vlan=0) # add to traffic stream pg0->pg1 pkts.extend(self.create_stream(self.pg0, self.pg1, packet_header, self.pg_packet_sizes, count)) # packets with SRH, segments-left 0, active segment a3::c4 # L2 has dot1q header packet_header = self.create_packet_header_IPv6_SRH_L2( sidlist=['a3::c4', 'a2::', 'a1::'], segleft=0, vlan=123) # add to traffic stream pg0->pg1 pkts.extend(self.create_stream(self.pg0, self.pg1, packet_header, self.pg_packet_sizes, count)) # packets without SRH, L2 in IPv6 # outer IPv6 dest addr is the localsid End.DX2 # L2 has no dot1q header packet_header = self.create_packet_header_IPv6_L2( dst_outer='a3::c4', vlan=0) # add to traffic stream pg0->pg1 pkts.extend(self.create_stream(self.pg0, self.pg1, packet_header, self.pg_packet_sizes, count)) # packets without SRH, L2 in IPv6 # outer IPv6 dest addr is the localsid End.DX2 # L2 has dot1q header packet_header = self.create_packet_header_IPv6_L2( dst_outer='a3::c4', vlan=123) # add to traffic stream pg0->pg1 pkts.extend(self.create_stream(self.pg0, self.pg1, packet_header, self.pg_packet_sizes, count)) # send packets and verify received packets self.send_and_verify_pkts(self.pg0, pkts, self.pg1, self.compare_rx_tx_packet_End_DX2) # log the localsid counters self.logger.info(self.vapi.cli("show sr localsid")) # remove SRv6 localSIDs localsid.remove_vpp_config() # cleanup interfaces self.teardown_interfaces() @unittest.skipUnless(0, "PC to fix") def test_SRv6_T_Insert_Classifier(self): """ Test SRv6 Transit.Insert behavior (IPv6 only). steer packets using the classifier """ # send traffic to one destination interface # source and destination are IPv6 only self.setup_interfaces(ipv6=[False, False, False, True, True]) # configure FIB entries route = VppIpRoute(self, "a4::", 64, [VppRoutePath(self.pg4.remote_ip6, self.pg4.sw_if_index, proto=DpoProto.DPO_PROTO_IP6)], is_ip6=1) route.add_vpp_config() # configure encaps IPv6 source address # needs to be done before SR Policy config # TODO: API? self.vapi.cli("set sr encaps source addr a3::") bsid = 'a3::9999:1' # configure SRv6 Policy # Note: segment list order: first -> last sr_policy = VppSRv6Policy( self, bsid=bsid, is_encap=0, sr_type=SRv6PolicyType.SR_POLICY_TYPE_DEFAULT, weight=1, fib_table=0, segments=['a4::', 'a5::', 'a6::c7'], source='a3::') sr_policy.add_vpp_config() self.sr_policy = sr_policy # log the sr policies self.logger.info(self.vapi.cli("show sr policies")) # add classify table # mask on dst ip address prefix a7::/8 mask = '{:0<16}'.format('ff') r = self.vapi.classify_add_del_table( 1, binascii.unhexlify(mask), match_n_vectors=(len(mask) - 1) // 32 + 1, current_data_flag=1, skip_n_vectors=2) # data offset self.assertIsNotNone(r, msg='No response msg for add_del_table') table_index = r.new_table_index # add the source routign node as a ip6 inacl netxt node r = self.vapi.add_node_next('ip6-inacl', 'sr-pl-rewrite-insert') inacl_next_node_index = r.node_index match = '{:0<16}'.format('a7') r = self.vapi.classify_add_del_session( 1, table_index, binascii.unhexlify(match), hit_next_index=inacl_next_node_index, action=3, metadata=0) # sr policy index self.assertIsNotNone(r, msg='No response msg for add_del_session') # log the classify table used in the steering policy self.logger.info(self.vapi.cli("show classify table")) r = self.vapi.input_acl_set_interface( is_add=1, sw_if_index=self.pg3.sw_if_index, ip6_table_index=table_index) self.assertIsNotNone(r, msg='No response msg for input_acl_set_interface') # log the ip6 inacl self.logger.info(self.vapi.cli("show inacl type ip6")) # create packets count = len(self.pg_packet_sizes) dst_inner = 'a7::1234' pkts = [] # create IPv6 packets without SRH packet_header = self.create_packet_header_IPv6(dst_inner) # create traffic stream pg3->pg4 pkts.extend(self.create_stream(self.pg3, self.pg4, packet_header, self.pg_packet_sizes, count)) # create IPv6 packets with SRH # packets with segments-left 1, active segment a7:: packet_header = self.create_packet_header_IPv6_SRH( sidlist=['a8::', 'a7::', 'a6::'], segleft=1) # create traffic stream pg3->pg4 pkts.extend(self.create_stream(self.pg3, self.pg4, packet_header, self.pg_packet_sizes, count)) # send packets and verify received packets self.send_and_verify_pkts(self.pg3, pkts, self.pg4, self.compare_rx_tx_packet_T_Insert) # remove the interface l2 input feature r = self.vapi.input_acl_set_interface( is_add=0, sw_if_index=self.pg3.sw_if_index, ip6_table_index=table_index) self.assertIsNotNone(r, msg='No response msg for input_acl_set_interface') # log the ip6 inacl after cleaning self.logger.info(self.vapi.cli("show inacl type ip6")) # log the localsid counters self.logger.info(self.vapi.cli("show sr localsid")) # remove classifier SR steering # classifier_steering.remove_vpp_config() self.logger.info(self.vapi.cli("show sr steering policies")) # remove SR Policies self.sr_policy.remove_vpp_config() self.logger.info(self.vapi.cli("show sr policies")) # remove classify session and table r = self.vapi.classify_add_del_session( 0, table_index, binascii.unhexlify(match)) self.assertIsNotNone(r, msg='No response msg for add_del_session') r = self.vapi.classify_add_del_table( 0, binascii.unhexlify(mask), table_index=table_index) self.assertIsNotNone(r, msg='No response msg for add_del_table') self.logger.info(self.vapi.cli("show classify table")) # remove FIB entries # done by tearDown # cleanup interfaces self.teardown_interfaces() def compare_rx_tx_packet_T_Encaps(self, tx_pkt, rx_pkt): """ Compare input and output packet after passing T.Encaps :param tx_pkt: transmitted packet :param rx_pkt: received packet """ # T.Encaps updates the headers as follows: # SR Policy seglist (S3, S2, S1) # SR Policy source C # IPv6: # in: IPv6(A, B2) # out: IPv6(C, S1)SRH(S3, S2, S1; SL=2)IPv6(A, B2) # IPv6 + SRH: # in: IPv6(A, B2)SRH(B3, B2, B1; SL=1) # out: IPv6(C, S1)SRH(S3, S2, S1; SL=2)IPv6(a, B2)SRH(B3, B2, B1; SL=1) # get first (outer) IPv6 header of rx'ed packet rx_ip = rx_pkt.getlayer(IPv6) rx_srh = None tx_ip = tx_pkt.getlayer(IPv6) # expected segment-list seglist = self.sr_policy.segments # reverse list to get order as in SRH tx_seglist = seglist[::-1] # get source address of SR Policy sr_policy_source = self.sr_policy.source # rx'ed packet should have SRH self.assertTrue(rx_pkt.haslayer(IPv6ExtHdrSegmentRouting)) # get SRH rx_srh = rx_pkt.getlayer(IPv6ExtHdrSegmentRouting) # received ip.src should be equal to SR Policy source self.assertEqual(rx_ip.src, sr_policy_source) # received ip.dst should be equal to expected sidlist[lastentry] self.assertEqual(rx_ip.dst, tx_seglist[-1]) # rx'ed seglist should be equal to expected seglist self.assertEqual(rx_srh.addresses, tx_seglist) # segleft should be equal to size expected seglist-1 self.assertEqual(rx_srh.segleft, len(tx_seglist)-1) # segleft should be equal to lastentry self.assertEqual(rx_srh.segleft, rx_srh.lastentry) # the whole rx'ed pkt beyond SRH should be equal to tx'ed pkt # except for the hop-limit field # -> update tx'ed hlim to the expected hlim tx_ip.hlim = tx_ip.hlim - 1 self.assertEqual(rx_srh.payload, tx_ip) self.logger.debug("packet verification: SUCCESS") def compare_rx_tx_packet_T_Encaps_IPv4(self, tx_pkt, rx_pkt): """ Compare input and output packet after passing T.Encaps for IPv4 :param tx_pkt: transmitted packet :param rx_pkt: received packet """ # T.Encaps for IPv4 updates the headers as follows: # SR Policy seglist (S3, S2, S1) # SR Policy source C # IPv4: # in: IPv4(A, B2) # out: IPv6(C, S1)SRH(S3, S2, S1; SL=2)IPv4(A, B2) # get first (outer) IPv6 header of rx'ed packet rx_ip = rx_pkt.getlayer(IPv6) rx_srh = None tx_ip = tx_pkt.getlayer(IP) # expected segment-list seglist = self.sr_policy.segments # reverse list to get order as in SRH tx_seglist = seglist[::-1] # get source address of SR Policy sr_policy_source = self.sr_policy.source # checks common to cases tx with and without SRH # rx'ed packet should have SRH and IPv4 header self.assertTrue(rx_pkt.haslayer(IPv6ExtHdrSegmentRouting)) self.assertTrue(rx_ip.payload.haslayer(IP)) # get SRH rx_srh = rx_pkt.getlayer(IPv6ExtHdrSegmentRouting) # received ip.src should be equal to SR Policy source self.assertEqual(rx_ip.src, sr_policy_source) # received ip.dst should be equal to sidlist[lastentry] self.assertEqual(rx_ip.dst, tx_seglist[-1]) # rx'ed seglist should be equal to seglist self.assertEqual(rx_srh.addresses, tx_seglist) # segleft should be equal to size seglist-1 self.assertEqual(rx_srh.segleft, len(tx_seglist)-1) # segleft should be equal to lastentry self.assertEqual(rx_srh.segleft, rx_srh.lastentry) # the whole rx'ed pkt beyond SRH should be equal to tx'ed pkt # except for the ttl field and ip checksum # -> adjust tx'ed ttl to expected ttl tx_ip.ttl = tx_ip.ttl - 1 # -> set tx'ed ip checksum to None and let scapy recompute tx_ip.chksum = None # read back the pkt (with str()) to force computing these fields # probably other ways to accomplish this are possible tx_ip = IP(str(tx_ip)) self.assertEqual(rx_srh.payload, tx_ip) self.logger.debug("packet verification: SUCCESS") def compare_rx_tx_packet_T_Encaps_L2(self, tx_pkt, rx_pkt): """ Compare input and output packet after passing T.Encaps for L2 :param tx_pkt: transmitted packet :param rx_pkt: received packet """ # T.Encaps for L2 updates the headers as follows: # SR Policy seglist (S3, S2, S1) # SR Policy source C # L2: # in: L2 # out: IPv6(C, S1)SRH(S3, S2, S1; SL=2)L2 # get first (outer) IPv6 header of rx'ed packet rx_ip = rx_pkt.getlayer(IPv6) rx_srh = None tx_ether = tx_pkt.getlayer(Ether) # expected segment-list seglist = self.sr_policy.segments # reverse list to get order as in SRH tx_seglist = seglist[::-1] # get source address of SR Policy sr_policy_source = self.sr_policy.source # rx'ed packet should have SRH self.assertTrue(rx_pkt.haslayer(IPv6ExtHdrSegmentRouting)) # get SRH rx_srh = rx_pkt.getlayer(IPv6ExtHdrSegmentRouting) # received ip.src should be equal to SR Policy source self.assertEqual(rx_ip.src, sr_policy_source) # received ip.dst should be equal to sidlist[lastentry] self.assertEqual(rx_ip.dst, tx_seglist[-1]) # rx'ed seglist should be equal to seglist self.assertEqual(rx_srh.addresses, tx_seglist) # segleft should be equal to size seglist-1 self.assertEqual(rx_srh.segleft, len(tx_seglist)-1) # segleft should be equal to lastentry self.assertEqual(rx_srh.segleft, rx_srh.lastentry) # nh should be "No Next Header" (59) self.assertEqual(rx_srh.nh, 59) # the whole rx'ed pkt beyond SRH should be equal to tx'ed pkt self.assertEqual(Ether(str(rx_srh.payload)), tx_ether) self.logger.debug("packet verification: SUCCESS") def compare_rx_tx_packet_T_Insert(self, tx_pkt, rx_pkt): """ Compare input and output packet after passing T.Insert :param tx_pkt: transmitted packet :param rx_pkt: received packet """ # T.Insert updates the headers as follows: # IPv6: # in: IPv6(A, B2) # out: IPv6(A, S1)SRH(B2, S3, S2, S1; SL=3) # IPv6 + SRH: # in: IPv6(A, B2)SRH(B3, B2, B1; SL=1) # out: IPv6(A, S1)SRH(B2, S3, S2, S1; SL=3)SRH(B3, B2, B1; SL=1) # get first (outer) IPv6 header of rx'ed packet rx_ip = rx_pkt.getlayer(IPv6) rx_srh = None rx_ip2 = None rx_srh2 = None rx_ip3 = None rx_udp = rx_pkt[UDP] tx_ip = tx_pkt.getlayer(IPv6) tx_srh = None tx_ip2 = None # some packets have been tx'ed with an SRH, some without it # get SRH if tx'ed packet has it if tx_pkt.haslayer(IPv6ExtHdrSegmentRouting): tx_srh = tx_pkt.getlayer(IPv6ExtHdrSegmentRouting) tx_ip2 = tx_pkt.getlayer(IPv6, 2) tx_udp = tx_pkt[UDP] # expected segment-list (make copy of SR Policy segment list) seglist = self.sr_policy.segments[:] # expected seglist has initial dest addr as last segment seglist.append(tx_ip.dst) # reverse list to get order as in SRH tx_seglist = seglist[::-1] # get source address of SR Policy sr_policy_source = self.sr_policy.source # checks common to cases tx with and without SRH # rx'ed packet should have SRH and only one IPv6 header self.assertTrue(rx_pkt.haslayer(IPv6ExtHdrSegmentRouting)) self.assertFalse(rx_ip.payload.haslayer(IPv6)) # get SRH rx_srh = rx_pkt.getlayer(IPv6ExtHdrSegmentRouting) # rx'ed ip.src should be equal to tx'ed ip.src self.assertEqual(rx_ip.src, tx_ip.src) # rx'ed ip.dst should be equal to sidlist[lastentry] self.assertEqual(rx_ip.dst, tx_seglist[-1]) # rx'ed seglist should be equal to expected seglist self.assertEqual(rx_srh.addresses, tx_seglist) # segleft should be equal to size(expected seglist)-1 self.assertEqual(rx_srh.segleft, len(tx_seglist)-1) # segleft should be equal to lastentry self.assertEqual(rx_srh.segleft, rx_srh.lastentry) if tx_srh: # packet was tx'ed with SRH # packet should have 2nd SRH self.assertTrue(rx_srh.payload.haslayer(IPv6ExtHdrSegmentRouting)) # get 2nd SRH rx_srh2 = rx_pkt.getlayer(IPv6ExtHdrSegmentRouting, 2) # rx'ed srh2.addresses should be equal to tx'ed srh.addresses self.assertEqual(rx_srh2.addresses, tx_srh.addresses) # rx'ed srh2.segleft should be equal to tx'ed srh.segleft self.assertEqual(rx_srh2.segleft, tx_srh.segleft) # rx'ed srh2.lastentry should be equal to tx'ed srh.lastentry self.assertEqual(rx_srh2.lastentry, tx_srh.lastentry) else: # packet was tx'ed without SRH # rx packet should have no other SRH self.assertFalse(rx_srh.payload.haslayer(IPv6ExtHdrSegmentRouting)) # UDP layer should be unchanged self.assertEqual(rx_udp, tx_udp) self.logger.debug("packet verification: SUCCESS") def compare_rx_tx_packet_End(self, tx_pkt, rx_pkt): """ Compare input and output packet after passing End (without PSP) :param tx_pkt: transmitted packet :param rx_pkt: received packet """ # End (no PSP) updates the headers as follows: # IPv6 + SRH: # in: IPv6(A, S1)SRH(S3, S2, S1; SL=2) # out: IPv6(A, S2)SRH(S3, S2, S1; SL=1) # get first (outer) IPv6 header of rx'ed packet rx_ip = rx_pkt.getlayer(IPv6) rx_srh = None rx_ip2 = None rx_udp = rx_pkt[UDP] tx_ip = tx_pkt.getlayer(IPv6) # we know the packet has been tx'ed # with an inner IPv6 header and an SRH tx_ip2 = tx_pkt.getlayer(IPv6, 2) tx_srh = tx_pkt.getlayer(IPv6ExtHdrSegmentRouting) tx_udp = tx_pkt[UDP] # common checks, regardless of tx segleft value # rx'ed packet should have 2nd IPv6 header self.assertTrue(rx_ip.payload.haslayer(IPv6)) # get second (inner) IPv6 header rx_ip2 = rx_pkt.getlayer(IPv6, 2) if tx_ip.segleft > 0: # SRH should NOT have been popped: # End SID without PSP does not pop SRH if segleft>0 self.assertTrue(rx_pkt.haslayer(IPv6ExtHdrSegmentRouting)) rx_srh = rx_pkt.getlayer(IPv6ExtHdrSegmentRouting) # received ip.src should be equal to expected ip.src self.assertEqual(rx_ip.src, tx_ip.src) # sidlist should be unchanged self.assertEqual(rx_srh.addresses, tx_srh.addresses) # segleft should have been decremented self.assertEqual(rx_srh.segleft, tx_srh.segleft-1) # received ip.dst should be equal to sidlist[segleft] self.assertEqual(rx_ip.dst, rx_srh.addresses[rx_srh.segleft]) # lastentry should be unchanged self.assertEqual(rx_srh.lastentry, tx_srh.lastentry) # inner IPv6 packet (ip2) should be unchanged self.assertEqual(rx_ip2.src, tx_ip2.src) self.assertEqual(rx_ip2.dst, tx_ip2.dst) # else: # tx_ip.segleft == 0 # TODO: Does this work with 2 SRHs in ingress packet? # UDP layer should be unchanged self.assertEqual(rx_udp, tx_udp) self.logger.debug("packet verification: SUCCESS") def compare_rx_tx_packet_End_PSP(self, tx_pkt, rx_pkt): """ Compare input and output packet after passing End with PSP :param tx_pkt: transmitted packet :param rx_pkt: received packet """ # End (PSP) updates the headers as follows: # IPv6 + SRH (SL>1): # in: IPv6(A, S1)SRH(S3, S2, S1; SL=2) # out: IPv6(A, S2)SRH(S3, S2, S1; SL=1) # IPv6 + SRH (SL=1): # in: IPv6(A, S2)SRH(S3, S2, S1; SL=1) # out: IPv6(A, S3) # get first (outer) IPv6 header of rx'ed packet rx_ip = rx_pkt.getlayer(IPv6) rx_srh = None rx_ip2 = None rx_udp = rx_pkt[UDP] tx_ip = tx_pkt.getlayer(IPv6) # we know the packet has been tx'ed # with an inner IPv6 header and an SRH tx_ip2 = tx_pkt.getlayer(IPv6, 2) tx_srh = tx_pkt.getlayer(IPv6ExtHdrSegmentRouting) tx_udp = tx_pkt[UDP] # common checks, regardless of tx segleft value self.assertTrue(rx_ip.payload.haslayer(IPv6)) rx_ip2 = rx_pkt.getlayer(IPv6, 2) # inner IPv6 packet (ip2) should be unchanged self.assertEqual(rx_ip2.src, tx_ip2.src) self.assertEqual(rx_ip2.dst, tx_ip2.dst) if tx_ip.segleft > 1: # SRH should NOT have been popped: # End SID with PSP does not pop SRH if segleft>1 # rx'ed packet should have SRH self.assertTrue(rx_pkt.haslayer(IPv6ExtHdrSegmentRouting)) rx_srh = rx_pkt.getlayer(IPv6ExtHdrSegmentRouting) # received ip.src should be equal to expected ip.src self.assertEqual(rx_ip.src, tx_ip.src) # sidlist should be unchanged self.assertEqual(rx_srh.addresses, tx_srh.addresses) # segleft should have been decremented self.assertEqual(rx_srh.segleft, tx_srh.segleft-1) # received ip.dst should be equal to sidlist[segleft] self.assertEqual(rx_ip.dst, rx_srh.addresses[rx_srh.segleft]) # lastentry should be unchanged self.assertEqual(rx_srh.lastentry, tx_srh.lastentry) else: # tx_ip.segleft <= 1 # SRH should have been popped: # End SID with PSP and segleft=1 pops SRH # the two IPv6 headers are still present # outer IPv6 header has DA == last segment of popped SRH # SRH should not be present self.assertFalse(rx_pkt.haslayer(IPv6ExtHdrSegmentRouting)) # outer IPv6 header ip.src should be equal to tx'ed ip.src self.assertEqual(rx_ip.src, tx_ip.src) # outer IPv6 header ip.dst should be = to tx'ed sidlist[segleft-1] self.assertEqual(rx_ip.dst, tx_srh.addresses[tx_srh.segleft-1]) # UDP layer should be unchanged self.assertEqual(rx_udp, tx_udp) self.logger.debug("packet verification: SUCCESS") def compare_rx_tx_packet_End_DX6(self, tx_pkt, rx_pkt): """ Compare input and output packet after passing End.DX6 :param tx_pkt: transmitted packet :param rx_pkt: received packet """ # End.DX6 updates the headers as follows: # IPv6 + SRH (SL=0): # in: IPv6(A, S3)SRH(S3, S2, S1; SL=0)IPv6(B, D) # out: IPv6(B, D) # IPv6: # in: IPv6(A, S3)IPv6(B, D) # out: IPv6(B, D) # get first (outer) IPv6 header of rx'ed packet rx_ip = rx_pkt.getlayer(IPv6) tx_ip = tx_pkt.getlayer(IPv6) tx_ip2 = tx_pkt.getlayer(IPv6, 2) # verify if rx'ed packet has no SRH self.assertFalse(rx_pkt.haslayer(IPv6ExtHdrSegmentRouting)) # the whole rx_ip pkt should be equal to tx_ip2 # except for the hlim field # -> adjust tx'ed hlim to expected hlim tx_ip2.hlim = tx_ip2.hlim - 1 self.assertEqual(rx_ip, tx_ip2) self.logger.debug("packet verification: SUCCESS") def compare_rx_tx_packet_End_DX4(self, tx_pkt, rx_pkt): """ Compare input and output packet after passing End.DX4 :param tx_pkt: transmitted packet :param rx_pkt: received packet """ # End.DX4 updates the headers as follows: # IPv6 + SRH (SL=0): # in: IPv6(A, S3)SRH(S3, S2, S1; SL=0)IPv4(B, D) # out: IPv4(B, D) # IPv6: # in: IPv6(A, S3)IPv4(B, D) # out: IPv4(B, D) # get IPv4 header of rx'ed packet rx_ip = rx_pkt.getlayer(IP) tx_ip = tx_pkt.getlayer(IPv6) tx_ip2 = tx_pkt.getlayer(IP) # verify if rx'ed packet has no SRH self.assertFalse(rx_pkt.haslayer(IPv6ExtHdrSegmentRouting)) # the whole rx_ip pkt should be equal to tx_ip2 # except for the ttl field and ip checksum # -> adjust tx'ed ttl to expected ttl tx_ip2.ttl = tx_ip2.ttl - 1 # -> set tx'ed ip checksum to None and let scapy recompute tx_ip2.chksum = None # read back the pkt (with str()) to force computing these fields # probably other ways to accomplish this are possible tx_ip2 = IP(str(tx_ip2)) self.assertEqual(rx_ip, tx_ip2) self.logger.debug("packet verification: SUCCESS") def compare_rx_tx_packet_End_DX2(self, tx_pkt, rx_pkt): """ Compare input and output packet after passing End.DX2 :param tx_pkt: transmitted packet :param rx_pkt: received packet """ # End.DX2 updates the headers as follows: # IPv6 + SRH (SL=0): # in: IPv6(A, S3)SRH(S3, S2, S1; SL=0)L2 # out: L2 # IPv6: # in: IPv6(A, S3)L2 # out: L2 # get IPv4 header of rx'ed packet rx_eth = rx_pkt.getlayer(Ether) tx_ip = tx_pkt.getlayer(IPv6) # we can't just get the 2nd Ether layer # get the Raw content and dissect it as Ether tx_eth1 = Ether(str(tx_pkt[Raw])) # verify if rx'ed packet has no SRH self.assertFalse(rx_pkt.haslayer(IPv6ExtHdrSegmentRouting)) # the whole rx_eth pkt should be equal to tx_eth1 self.assertEqual(rx_eth, tx_eth1) self.logger.debug("packet verification: SUCCESS") def create_stream(self, src_if, dst_if, packet_header, packet_sizes, count): """Create SRv6 input packet stream for defined interface. :param VppInterface src_if: Interface to create packet stream for :param VppInterface dst_if: destination interface of packet stream :param packet_header: Layer3 scapy packet headers, L2 is added when not provided, Raw(payload) with packet_info is added :param list packet_sizes: packet stream pckt sizes,sequentially applied to packets in stream have :param int count: number of packets in packet stream :return: list of packets """ self.logger.info("Creating packets") pkts = [] for i in range(0, count-1): payload_info = self.create_packet_info(src_if, dst_if) self.logger.debug( "Creating packet with index %d" % (payload_info.index)) payload = self.info_to_payload(payload_info) # add L2 header if not yet provided in packet_header if packet_header.getlayer(0).name == 'Ethernet': p = (packet_header / Raw(payload)) else: p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) / packet_header / Raw(payload)) size = packet_sizes[i % len(packet_sizes)] self.logger.debug("Packet size %d" % (size)) self.extend_packet(p, size) # we need to store the packet with the automatic fields computed # read back the dumped packet (with str()) # to force computing these fields # probably other ways are possible p = Ether(str(p)) payload_info.data = p.copy() self.logger.debug(ppp("Created packet:", p)) pkts.append(p) self.logger.info("Done creating packets") return pkts def send_and_verify_pkts(self, input, pkts, output, compare_func): """Send packets and verify received packets using compare_func :param input: ingress interface of DUT :param pkts: list of packets to transmit :param output: egress interface of DUT :param compare_func: function to compare in and out packets """ # add traffic stream to input interface input.add_stream(pkts) # enable capture on all interfaces self.pg_enable_capture(self.pg_interfaces) # start traffic self.logger.info("Starting traffic") self.pg_start() # get output capture self.logger.info("Getting packet capture") capture = output.get_capture() # assert nothing was captured on input interface input.assert_nothing_captured() # verify captured packets self.verify_captured_pkts(output, capture, compare_func) def create_packet_header_IPv6(self, dst): """Create packet header: IPv6 header, UDP header :param dst: IPv6 destination address IPv6 source address is 1234::1 UDP source port and destination port are 1234 """ p = (IPv6(src='1234::1', dst=dst) / UDP(sport=1234, dport=1234)) return p def create_packet_header_IPv6_SRH(self, sidlist, segleft): """Create packet header: IPv6 header with SRH, UDP header :param list sidlist: segment list :param int segleft: segments-left field value IPv6 destination address is set to sidlist[segleft] IPv6 source addresses are 1234::1 and 4321::1 UDP source port and destination port are 1234 """ p = (IPv6(src='1234::1', dst=sidlist[segleft]) / IPv6ExtHdrSegmentRouting(addresses=sidlist) / UDP(sport=1234, dport=1234)) return p def create_packet_header_IPv6_SRH_IPv6(self, dst, sidlist, segleft): """Create packet header: IPv6 encapsulated in SRv6: IPv6 header with SRH, IPv6 header, UDP header :param ipv6address dst: inner IPv6 destination address :param list sidlist: segment list of outer IPv6 SRH :param int segleft: segments-left field of outer IPv6 SRH Outer IPv6 destination address is set to sidlist[segleft] IPv6 source addresses are 1234::1 and 4321::1 UDP source port and destination port are 1234 """ p = (IPv6(src='1234::1', dst=sidlist[segleft]) / IPv6ExtHdrSegmentRouting(addresses=sidlist, segleft=segleft, nh=41) / IPv6(src='4321::1', dst=dst) / UDP(sport=1234, dport=1234)) return p def create_packet_header_IPv6_IPv6(self, dst_inner, dst_outer): """Create packet header: IPv6 encapsulated in IPv6: IPv6 header, IPv6 header, UDP header :param ipv6address dst_inner: inner IPv6 destination address :param ipv6address dst_outer: outer IPv6 destination address IPv6 source addresses are 1234::1 and 4321::1 UDP source port and destination port are 1234 """ p = (IPv6(src='1234::1', dst=dst_outer) / IPv6(src='4321::1', dst=dst_inner) / UDP(sport=1234, dport=1234)) return p def create_packet_header_IPv6_SRH_SRH_IPv6(self, dst, sidlist1, segleft1, sidlist2, segleft2): """Create packet header: IPv6 encapsulated in SRv6 with 2 SRH: IPv6 header with SRH, 2nd SRH, IPv6 header, UDP header :param ipv6address dst: inner IPv6 destination address :param list sidlist1: segment list of outer IPv6 SRH :param int segleft1: segments-left field of outer IPv6 SRH :param list sidlist2: segment list of inner IPv6 SRH :param int segleft2: segments-left field of inner IPv6 SRH Outer IPv6 destination address is set to sidlist[segleft] IPv6 source addresses are 1234::1 and 4321::1 UDP source port and destination port are 1234 """ p = (IPv6(src='1234::1', dst=sidlist1[segleft1]) / IPv6ExtHdrSegmentRouting(addresses=sidlist1, segleft=segleft1, nh=43) / IPv6ExtHdrSegmentRouting(addresses=sidlist2, segleft=segleft2, nh=41) / IPv6(src='4321::1', dst=dst) / UDP(sport=1234, dport=1234)) return p def create_packet_header_IPv4(self, dst): """Create packet header: IPv4 header, UDP header :param dst: IPv4 destination address IPv4 source address is 123.1.1.1 UDP source port and destination port are 1234 """ p = (IP(src='123.1.1.1', dst=dst) / UDP(sport=1234, dport=1234)) return p def create_packet_header_IPv6_IPv4(self, dst_inner, dst_outer): """Create packet header: IPv4 encapsulated in IPv6: IPv6 header, IPv4 header, UDP header :param ipv4address dst_inner: inner IPv4 destination address :param ipv6address dst_outer: outer IPv6 destination address IPv6 source address is 1234::1 IPv4 source address is 123.1.1.1 UDP source port and destination port are 1234 """ p = (IPv6(src='1234::1', dst=dst_outer) / IP(src='123.1.1.1', dst=dst_inner) / UDP(sport=1234, dport=1234)) return p def create_packet_header_IPv6_SRH_IPv4(self, dst, sidlist, segleft): """Create packet header: IPv4 encapsulated in SRv6: IPv6 header with SRH, IPv4 header, UDP header :param ipv4address dst: inner IPv4 destination address :param list sidlist: segment list of outer IPv6 SRH :param int segleft: segments-left field of outer IPv6 SRH Outer IPv6 destination address is set to sidlist[segleft] IPv6 source address is 1234::1 IPv4 source address is 123.1.1.1 UDP source port and destination port are 1234 """ p = (IPv6(src='1234::1', dst=sidlist[segleft]) / IPv6ExtHdrSegmentRouting(addresses=sidlist, segleft=segleft, nh=4) / IP(src='123.1.1.1', dst=dst) / UDP(sport=1234, dport=1234)) return p def create_packet_header_L2(self, vlan=0): """Create packet header: L2 header :param vlan: if vlan!=0 then add 802.1q header """ # Note: the dst addr ('00:55:44:33:22:11') is used in # the compare function compare_rx_tx_packet_T_Encaps_L2 # to detect presence of L2 in SRH payload p = Ether(src='00:11:22:33:44:55', dst='00:55:44:33:22:11') etype = 0x8137 # IPX if vlan: # add 802.1q layer p /= Dot1Q(vlan=vlan, type=etype) else: p.type = etype return p def create_packet_header_IPv6_SRH_L2(self, sidlist, segleft, vlan=0): """Create packet header: L2 encapsulated in SRv6: IPv6 header with SRH, L2 :param list sidlist: segment list of outer IPv6 SRH :param int segleft: segments-left field of outer IPv6 SRH :param vlan: L2 vlan; if vlan!=0 then add 802.1q header Outer IPv6 destination address is set to sidlist[segleft] IPv6 source address is 1234::1 """ eth = Ether(src='00:11:22:33:44:55', dst='00:55:44:33:22:11') etype = 0x8137 # IPX if vlan: # add 802.1q layer eth /= Dot1Q(vlan=vlan, type=etype) else: eth.type = etype p = (IPv6(src='1234::1', dst=sidlist[segleft]) / IPv6ExtHdrSegmentRouting(addresses=sidlist, segleft=segleft, nh=59) / eth) return p def create_packet_header_IPv6_L2(self, dst_outer, vlan=0): """Create packet header: L2 encapsulated in IPv6: IPv6 header, L2 :param ipv6address dst_outer: outer IPv6 destination address :param vlan: L2 vlan; if vlan!=0 then add 802.1q header """ eth = Ether(src='00:11:22:33:44:55', dst='00:55:44:33:22:11') etype = 0x8137 # IPX if vlan: # add 802.1q layer eth /= Dot1Q(vlan=vlan, type=etype) else: eth.type = etype p = (IPv6(src='1234::1', dst=dst_outer, nh=59) / eth) return p def get_payload_info(self, packet): """ Extract the payload_info from the packet """ # in most cases, payload_info is in packet[Raw] # but packet[Raw] gives the complete payload # (incl L2 header) for the T.Encaps L2 case try: payload_info = self.payload_to_info(str(packet[Raw])) except: # remote L2 header from packet[Raw]: # take packet[Raw], convert it to an Ether layer # and then extract Raw from it payload_info = self.payload_to_info( str(Ether(str(packet[Raw]))[Raw])) return payload_info def verify_captured_pkts(self, dst_if, capture, compare_func): """ Verify captured packet stream for specified interface. Compare ingress with egress packets using the specified compare fn :param dst_if: egress interface of DUT :param capture: captured packets :param compare_func: function to compare in and out packet """ self.logger.info("Verifying capture on interface %s using function %s" % (dst_if.name, compare_func.func_name)) last_info = dict() for i in self.pg_interfaces: last_info[i.sw_if_index] = None dst_sw_if_index = dst_if.sw_if_index for packet in capture: try: # extract payload_info from packet's payload payload_info = self.get_payload_info(packet) packet_index = payload_info.index self.logger.debug("Verifying packet with index %d" % (packet_index)) # packet should have arrived on the expected interface self.assertEqual(payload_info.dst, dst_sw_if_index) self.logger.debug( "Got packet on interface %s: src=%u (idx=%u)" % (dst_if.name, payload_info.src, packet_index)) # search for payload_info with same src and dst if_index # this will give us the transmitted packet next_info = self.get_next_packet_info_for_interface2( payload_info.src, dst_sw_if_index, last_info[payload_info.src]) last_info[payload_info.src] = next_info # next_info should not be None self.assertTrue(next_info is not None) # index of tx and rx packets should be equal self.assertEqual(packet_index, next_info.index) # data field of next_info contains the tx packet txed_packet = next_info.data self.logger.debug(ppp("Transmitted packet:", txed_packet)) # ppp=Pretty Print Packet self.logger.debug(ppp("Received packet:", packet)) # compare rcvd packet with expected packet using compare_func compare_func(txed_packet, packet) except: print packet.command() self.logger.error(ppp("Unexpected or invalid packet:", packet)) raise # have all expected packets arrived? for i in self.pg_interfaces: remaining_packet = self.get_next_packet_info_for_interface2( i.sw_if_index, dst_sw_if_index, last_info[i.sw_if_index]) self.assertTrue(remaining_packet is None, "Interface %s: Packet expected from interface %s " "didn't arrive" % (dst_if.name, i.name)) if __name__ == '__main__': unittest.main(testRunner=VppTestRunner)