summaryrefslogtreecommitdiffstats
path: root/isisd/isis_srv6.c
blob: 1b0c706946a15e31e2182ee9efa99bf2d75fd7d8 (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
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * This is an implementation of Segment Routing over IPv6 (SRv6) for IS-IS
 * as per RFC 9352
 * https://datatracker.ietf.org/doc/html/rfc9352
 *
 * Copyright (C) 2023 Carmine Scarpitta - University of Rome Tor Vergata
 */

#include <zebra.h>

#include "srv6.h"
#include "termtable.h"
#include "lib/lib_errors.h"

#include "isisd/isisd.h"
#include "isisd/isis_adjacency.h"
#include "isisd/isis_misc.h"
#include "isisd/isis_route.h"
#include "isisd/isis_srv6.h"
#include "isisd/isis_zebra.h"

/* Local variables and functions */
DEFINE_MTYPE_STATIC(ISISD, ISIS_SRV6_SID, "ISIS SRv6 Segment ID");
DEFINE_MTYPE_STATIC(ISISD, ISIS_SRV6_INFO, "ISIS SRv6 information");

/**
 * Fill in SRv6 SID Structure Sub-Sub-TLV with information from an SRv6 SID.
 *
 * @param sid				    SRv6 SID configuration
 * @param structure_subsubtlv	SRv6 SID Structure Sub-Sub-TLV to be updated
 */
void isis_srv6_sid_structure2subsubtlv(
	const struct isis_srv6_sid *sid,
	struct isis_srv6_sid_structure_subsubtlv *structure_subsubtlv)
{
	/* Set Locator Block length */
	structure_subsubtlv->loc_block_len = sid->structure.loc_block_len;

	/* Set Locator Node length */
	structure_subsubtlv->loc_node_len = sid->structure.loc_node_len;

	/* Set Function length */
	structure_subsubtlv->func_len = sid->structure.func_len;

	/* Set Argument length */
	structure_subsubtlv->arg_len = sid->structure.arg_len;
}

/**
 * Fill in SRv6 End SID Sub-TLV with information from an SRv6 SID.
 *
 * @param sid	      SRv6 SID configuration
 * @param sid_subtlv  SRv6 End SID Sub-TLV to be updated
 */
void isis_srv6_end_sid2subtlv(const struct isis_srv6_sid *sid,
			      struct isis_srv6_end_sid_subtlv *sid_subtlv)
{
	/* Set SRv6 SID flags */
	sid_subtlv->flags = sid->flags;

	/* Set SRv6 SID behavior */
	sid_subtlv->behavior = sid->behavior;

	/* Set SRv6 SID value */
	sid_subtlv->sid = sid->sid;
}

/**
 * Fill in SRv6 Locator TLV with information from an SRv6 locator.
 *
 * @param loc	     SRv6 Locator configuration
 * @param loc_tlv    SRv6 Locator TLV to be updated
 */
void isis_srv6_locator2tlv(const struct isis_srv6_locator *loc,
			   struct isis_srv6_locator_tlv *loc_tlv)
{
	/* Set SRv6 Locator metric */
	loc_tlv->metric = loc->metric;

	/* Set SRv6 Locator flags */
	loc_tlv->flags = loc->flags;

	/* Set SRv6 Locator algorithm */
	loc_tlv->algorithm = loc->algorithm;

	/* Set SRv6 Locator prefix */
	loc_tlv->prefix = loc->prefix;
}

/**
 * Unset the SRv6 locator for a given IS-IS area.
 *
 * @param area	IS-IS area
 *
 * @result True on success, False otherwise
 */
bool isis_srv6_locator_unset(struct isis_area *area)
{
	int ret;
	struct listnode *node, *nnode;
	struct srv6_locator_chunk *chunk;
	struct isis_srv6_sid *sid;
	struct srv6_adjacency *sra;

	if (strncmp(area->srv6db.config.srv6_locator_name, "",
		    sizeof(area->srv6db.config.srv6_locator_name)) == 0) {
		sr_debug("SRv6 locator not set");
		return true;
	}

	/* Delete SRv6 SIDs */
	for (ALL_LIST_ELEMENTS(area->srv6db.srv6_sids, node, nnode, sid)) {
		sr_debug(
			"Deleting SRv6 SID (locator %s, sid %pI6) from IS-IS area %s",
			area->srv6db.config.srv6_locator_name, &sid->sid,
			area->area_tag);

		/* Uninstall the SRv6 SID from the forwarding plane through
		 * Zebra */
		isis_zebra_srv6_sid_uninstall(area, sid);

		listnode_delete(area->srv6db.srv6_sids, sid);
		isis_srv6_sid_free(sid);
	}

	/* Uninstall all local Adjacency-SIDs. */
	for (ALL_LIST_ELEMENTS(area->srv6db.srv6_endx_sids, node, nnode, sra))
		srv6_endx_sid_del(sra);

	/* Inform Zebra that we are releasing the SRv6 locator */
	ret = isis_zebra_srv6_manager_release_locator_chunk(
		area->srv6db.config.srv6_locator_name);
	if (ret < 0)
		return false;

	/* Delete chunks */
	for (ALL_LIST_ELEMENTS(area->srv6db.srv6_locator_chunks, node, nnode,
			       chunk)) {
		sr_debug(
			"Releasing chunk of locator %s (prefix %pFX) for IS-IS area %s",
			area->srv6db.config.srv6_locator_name, &chunk->prefix,
			area->area_tag);

		listnode_delete(area->srv6db.srv6_locator_chunks, chunk);
		srv6_locator_chunk_free(&chunk);
	}

	/* Clear locator name */
	memset(area->srv6db.config.srv6_locator_name, 0,
	       sizeof(area->srv6db.config.srv6_locator_name));

	/* Regenerate LSPs to advertise that the SRv6 locator no longer exists
	 */
	lsp_regenerate_schedule(area, area->is_type, 0);

	return true;
}

/**
 * Set the interface used to install SRv6 SIDs into the data plane.
 *
 * @param area	IS-IS area
 */
void isis_srv6_interface_set(struct isis_area *area, const char *ifname)
{
	struct listnode *node;
	struct isis_srv6_sid *sid;

	if (!ifname)
		return;

	if (!strncmp(ifname, area->srv6db.config.srv6_ifname, IF_NAMESIZE)) {
		/* The interface has not changed, nothing to do */
		return;
	}

	sr_debug("SRv6 interface for IS-IS area %s changed (old interface: %s, new interface: %s)", area->area_tag, area->srv6db.config.srv6_ifname, ifname);

	/* Walk through all SIDs and uninstall them from the data plane */
	for (ALL_LIST_ELEMENTS_RO(area->srv6db.srv6_sids, node, sid)) {
		sr_debug("Uninstalling SID %pI6 from the data plane", &sid->sid);
		isis_zebra_srv6_sid_uninstall(area, sid);
	}

	strlcpy(area->srv6db.config.srv6_ifname, ifname, sizeof(area->srv6db.config.srv6_ifname));

	if (!if_lookup_by_name(area->srv6db.config.srv6_ifname, VRF_DEFAULT)) {
		sr_debug("Interface %s not yet exist in data plane, deferring SIDs installation until it's created", area->srv6db.config.srv6_ifname);
		return;
	}

	/* Walk through all SIDs and re-install them into the data plane with the newly configured interface */
	for (ALL_LIST_ELEMENTS_RO(area->srv6db.srv6_sids, node, sid)) {
		sr_debug("Installing SID %pI6 from the data plane", &sid->sid);
		isis_zebra_srv6_sid_install(area, sid);
	}
}

/**
 * Encode SID function in the SRv6 SID.
 *
 * @param sid
 * @param func
 * @param offset
 * @param len
 */
static void encode_sid_func(struct in6_addr *sid, uint32_t func, uint8_t offset,
			    uint8_t len)
{
	for (uint8_t idx = 0; idx < len; idx++) {
		uint8_t tidx = offset + idx;
		sid->s6_addr[tidx / 8] &= ~(0x1 << (7 - tidx % 8));
		if (func >> (len - 1 - idx) & 0x1)
			sid->s6_addr[tidx / 8] |= 0x1 << (7 - tidx % 8);
	}
}

static bool sid_exist(struct isis_area *area, const struct in6_addr *sid)
{
	struct listnode *node;
	struct isis_srv6_sid *s;
	struct srv6_adjacency *sra;

	for (ALL_LIST_ELEMENTS_RO(area->srv6db.srv6_sids, node, s))
		if (sid_same(&s->sid, sid))
			return true;
	for (ALL_LIST_ELEMENTS_RO(area->srv6db.srv6_endx_sids, node, sra))
		if (sid_same(&sra->sid, sid))
			return true;
	return false;
}

/**
 * Request a SID from the SRv6 locator.
 *
 * @param area		IS-IS area
 * @param chunk		SRv6 locator chunk
 * @param sid_func	The FUNCTION part of the SID to be allocated (a negative
 * number will allocate the first available SID)
 *
 * @return	First available SID on success or in6addr_any if the SRv6
 * locator chunk is full
 */
static struct in6_addr
srv6_locator_request_sid(struct isis_area *area,
			 struct srv6_locator_chunk *chunk, int sid_func)
{
	struct in6_addr sid;
	uint8_t offset = 0;
	uint8_t func_len = 0;
	uint32_t func_max;
	bool allocated = false;

	if (!area || !chunk)
		return in6addr_any;

	sr_debug("ISIS-SRv6 (%s): requested new SID from locator %s",
		 area->area_tag, chunk->locator_name);

	/* Let's build the SID, step by step. A SID has the following structure
	(defined in RFC 8986): LOCATOR:FUNCTION:ARGUMENT.*/

	/* First, we encode the LOCATOR in the L most significant bits. */
	sid = chunk->prefix.prefix;

	/* The next part of the SID is the FUNCTION. Let's compute the length
	 * and the offset of the FUNCTION in the SID */
	func_len = chunk->function_bits_length;
	offset = chunk->block_bits_length + chunk->node_bits_length;

	/* Then, encode the FUNCTION */
	if (sid_func >= 0) {
		/* SID FUNCTION has been specified. We need to allocate a SID
		 * with the requested FUNCTION. */
		encode_sid_func(&sid, sid_func, offset, func_len);
		if (sid_exist(area, &sid)) {
			zlog_warn(
				"ISIS-SRv6 (%s): the requested SID %pI6 is already used",
				area->area_tag, &sid);
			return sid;
		}
		allocated = true;
	} else {
		/* SID FUNCTION not specified. We need to choose a FUNCTION that
		 * is not already used. So let's iterate through all possible
		 * functions and get the first available one. */
		func_max = (1 << func_len) - 1;
		for (uint32_t func = 1; func < func_max; func++) {
			encode_sid_func(&sid, func, offset, func_len);
			if (sid_exist(area, &sid))
				continue;
			allocated = true;
			break;
		}
	}

	if (!allocated) {
		/* We ran out of available SIDs */
		zlog_warn("ISIS-SRv6 (%s): no SIDs available in locator %s",
			  area->area_tag, chunk->locator_name);
		return in6addr_any;
	}

	sr_debug("ISIS-SRv6 (%s): allocating new SID %pI6", area->area_tag,
		 &sid);

	return sid;
}

/**
 * Allocate an SRv6 SID from an SRv6 locator.
 *
 * @param area		IS-IS area
 * @param chunk		SRv6 locator chunk
 * @param behavior	SRv6 Endpoint Behavior bound to the SID
 *
 * @result the allocated SID on success, NULL otherwise
 */
struct isis_srv6_sid *
isis_srv6_sid_alloc(struct isis_area *area, struct srv6_locator_chunk *chunk,
		    enum srv6_endpoint_behavior_codepoint behavior,
		    int sid_func)
{
	struct isis_srv6_sid *sid = NULL;

	if (!area || !chunk)
		return NULL;

	sid = XCALLOC(MTYPE_ISIS_SRV6_SID, sizeof(struct isis_srv6_sid));

	sid->sid = srv6_locator_request_sid(area, chunk, sid_func);
	if (IPV6_ADDR_SAME(&sid->sid, &in6addr_any)) {
		isis_srv6_sid_free(sid);
		return NULL;
	}

	sid->behavior = behavior;
	sid->structure.loc_block_len = chunk->block_bits_length;
	sid->structure.loc_node_len = chunk->node_bits_length;
	sid->structure.func_len = chunk->function_bits_length;
	sid->structure.arg_len = chunk->argument_bits_length;
	sid->locator = chunk;
	sid->area = area;

	return sid;
}

void isis_srv6_sid_free(struct isis_srv6_sid *sid)
{
	XFREE(MTYPE_ISIS_SRV6_SID, sid);
}

/**
 * Delete all backup SRv6 End.X SIDs.
 *
 * @param area	IS-IS area
 * @param level	IS-IS level
 */
void isis_area_delete_backup_srv6_endx_sids(struct isis_area *area, int level)
{
	struct srv6_adjacency *sra;
	struct listnode *node, *nnode;

	for (ALL_LIST_ELEMENTS(area->srv6db.srv6_endx_sids, node, nnode, sra))
		if (sra->type == ISIS_SRV6_LAN_BACKUP &&
		    (sra->adj->level & level))
			srv6_endx_sid_del(sra);
}

/* --- SRv6 End.X SID management functions ------------------- */

/**
 * Add new local End.X SID.
 *
 * @param adj	   IS-IS Adjacency
 * @param backup   True to initialize backup Adjacency SID
 * @param nexthops List of backup nexthops (for backup End.X SIDs only)
 */
void srv6_endx_sid_add_single(struct isis_adjacency *adj, bool backup,
			      struct list *nexthops)
{
	struct isis_circuit *circuit = adj->circuit;
	struct isis_area *area = circuit->area;
	struct srv6_adjacency *sra;
	struct isis_srv6_endx_sid_subtlv *adj_sid;
	struct isis_srv6_lan_endx_sid_subtlv *ladj_sid;
	struct in6_addr nexthop;
	uint8_t flags = 0;
	struct srv6_locator_chunk *chunk;
	uint32_t behavior;

	if (!area || !area->srv6db.srv6_locator_chunks ||
	    list_isempty(area->srv6db.srv6_locator_chunks))
		return;

	sr_debug("ISIS-SRv6 (%s): Add %s End.X SID", area->area_tag,
		 backup ? "Backup" : "Primary");

	/* Determine nexthop IP address */
	if (!circuit->ipv6_router || !adj->ll_ipv6_count)
		return;

	chunk = (struct srv6_locator_chunk *)listgetdata(
		listhead(area->srv6db.srv6_locator_chunks));
	if (!chunk)
		return;

	nexthop = adj->ll_ipv6_addrs[0];

	/* Prepare SRv6 End.X as per RFC9352 section #8.1 */
	if (backup)
		SET_FLAG(flags, EXT_SUBTLV_LINK_SRV6_ENDX_SID_BFLG);

	if (circuit->ext == NULL)
		circuit->ext = isis_alloc_ext_subtlvs();

	behavior = (CHECK_FLAG(chunk->flags, SRV6_LOCATOR_USID))
			   ? SRV6_ENDPOINT_BEHAVIOR_END_X_NEXT_CSID
			   : SRV6_ENDPOINT_BEHAVIOR_END_X;

	sra = XCALLOC(MTYPE_ISIS_SRV6_INFO, sizeof(*sra));
	sra->type = backup ? ISIS_SRV6_LAN_BACKUP : ISIS_SRV6_ADJ_NORMAL;
	sra->behavior = behavior;
	sra->locator = chunk;
	sra->structure.loc_block_len = chunk->block_bits_length;
	sra->structure.loc_node_len = chunk->node_bits_length;
	sra->structure.func_len = chunk->function_bits_length;
	sra->structure.arg_len = chunk->argument_bits_length;
	sra->nexthop = nexthop;

	sra->sid = srv6_locator_request_sid(area, chunk, -1);
	if (IPV6_ADDR_SAME(&sra->sid, &in6addr_any)) {
		XFREE(MTYPE_ISIS_SRV6_INFO, sra);
		return;
	}

	switch (circuit->circ_type) {
	/* SRv6 LAN End.X SID for Broadcast interface section #8.2 */
	case CIRCUIT_T_BROADCAST:
		ladj_sid = XCALLOC(MTYPE_ISIS_SUBTLV, sizeof(*ladj_sid));
		memcpy(ladj_sid->neighbor_id, adj->sysid,
		       sizeof(ladj_sid->neighbor_id));
		ladj_sid->flags = flags;
		ladj_sid->algorithm = SR_ALGORITHM_SPF;
		ladj_sid->weight = 0;
		ladj_sid->behavior = sra->behavior;
		ladj_sid->sid = sra->sid;
		ladj_sid->subsubtlvs = isis_alloc_subsubtlvs(
			ISIS_CONTEXT_SUBSUBTLV_SRV6_ENDX_SID);
		ladj_sid->subsubtlvs->srv6_sid_structure = XCALLOC(
			MTYPE_ISIS_SUBSUBTLV,
			sizeof(*ladj_sid->subsubtlvs->srv6_sid_structure));
		ladj_sid->subsubtlvs->srv6_sid_structure->loc_block_len =
			sra->structure.loc_block_len;
		ladj_sid->subsubtlvs->srv6_sid_structure->loc_node_len =
			sra->structure.loc_node_len;
		ladj_sid->subsubtlvs->srv6_sid_structure->func_len =
			sra->structure.func_len;
		ladj_sid->subsubtlvs->srv6_sid_structure->arg_len =
			sra->structure.arg_len;
		isis_tlvs_add_srv6_lan_endx_sid(circuit->ext, ladj_sid);
		sra->u.lendx_sid = ladj_sid;
		break;
	/* SRv6 End.X SID for Point to Point interface section #8.1 */
	case CIRCUIT_T_P2P:
		adj_sid = XCALLOC(MTYPE_ISIS_SUBTLV, sizeof(*adj_sid));
		adj_sid->flags = flags;
		adj_sid->algorithm = SR_ALGORITHM_SPF;
		adj_sid->weight = 0;
		adj_sid->behavior = sra->behavior;
		adj_sid->sid = sra->sid;
		adj_sid->subsubtlvs = isis_alloc_subsubtlvs(
			ISIS_CONTEXT_SUBSUBTLV_SRV6_ENDX_SID);
		adj_sid->subsubtlvs->srv6_sid_structure = XCALLOC(
			MTYPE_ISIS_SUBSUBTLV,
			sizeof(*adj_sid->subsubtlvs->srv6_sid_structure));
		adj_sid->subsubtlvs->srv6_sid_structure->loc_block_len =
			sra->structure.loc_block_len;
		adj_sid->subsubtlvs->srv6_sid_structure->loc_node_len =
			sra->structure.loc_node_len;
		adj_sid->subsubtlvs->srv6_sid_structure->func_len =
			sra->structure.func_len;
		adj_sid->subsubtlvs->srv6_sid_structure->arg_len =
			sra->structure.arg_len;
		isis_tlvs_add_srv6_endx_sid(circuit->ext, adj_sid);
		sra->u.endx_sid = adj_sid;
		break;
	default:
		flog_err(EC_LIB_DEVELOPMENT, "%s: unexpected circuit type: %u",
			 __func__, circuit->circ_type);
		exit(1);
	}

	/* Add Adjacency-SID in SRDB */
	sra->adj = adj;
	listnode_add(area->srv6db.srv6_endx_sids, sra);
	listnode_add(adj->srv6_endx_sids, sra);

	isis_zebra_srv6_adj_sid_install(sra);
}

/**
 * Add Primary and Backup local SRv6 End.X SID.
 *
 * @param adj	  IS-IS Adjacency
 */
void srv6_endx_sid_add(struct isis_adjacency *adj)
{
	srv6_endx_sid_add_single(adj, false, NULL);
}

/**
 * Delete local SRv6 End.X SID.
 *
 * @param sra	SRv6 Adjacency
 */
void srv6_endx_sid_del(struct srv6_adjacency *sra)
{
	struct isis_circuit *circuit = sra->adj->circuit;
	struct isis_area *area = circuit->area;

	sr_debug("ISIS-SRv6 (%s): Delete SRv6 End.X SID", area->area_tag);

	isis_zebra_srv6_adj_sid_uninstall(sra);

	/* Release dynamic SRv6 SID and remove subTLVs */
	switch (circuit->circ_type) {
	case CIRCUIT_T_BROADCAST:
		isis_tlvs_del_srv6_lan_endx_sid(circuit->ext, sra->u.lendx_sid);
		break;
	case CIRCUIT_T_P2P:
		isis_tlvs_del_srv6_endx_sid(circuit->ext, sra->u.endx_sid);
		break;
	default:
		flog_err(EC_LIB_DEVELOPMENT, "%s: unexpected circuit type: %u",
			 __func__, circuit->circ_type);
		exit(1);
	}

	if (sra->type == ISIS_SRV6_LAN_BACKUP && sra->backup_nexthops) {
		sra->backup_nexthops->del =
			(void (*)(void *))isis_nexthop_delete;
		list_delete(&sra->backup_nexthops);
	}

	/* Remove Adjacency-SID from the SRDB */
	listnode_delete(area->srv6db.srv6_endx_sids, sra);
	listnode_delete(sra->adj->srv6_endx_sids, sra);
	XFREE(MTYPE_ISIS_SRV6_INFO, sra);
}

/**
 * Lookup SRv6 End.X SID by type.
 *
 * @param adj	  IS-IS Adjacency
 * @param type    SRv6 End.X SID type
 */
struct srv6_adjacency *isis_srv6_endx_sid_find(struct isis_adjacency *adj,
					       enum srv6_adj_type type)
{
	struct srv6_adjacency *sra;
	struct listnode *node;

	for (ALL_LIST_ELEMENTS_RO(adj->srv6_endx_sids, node, sra))
		if (sra->type == type)
			return sra;

	return NULL;
}

/**
 * Remove all SRv6 End.X SIDs associated to an adjacency that is going down.
 *
 * @param adj	IS-IS Adjacency
 *
 * @return	0
 */
static int srv6_adj_state_change(struct isis_adjacency *adj)
{
	struct srv6_adjacency *sra;
	struct listnode *node, *nnode;

	if (!adj->circuit->area->srv6db.config.enabled)
		return 0;

	if (adj->adj_state == ISIS_ADJ_UP)
		return 0;

	for (ALL_LIST_ELEMENTS(adj->srv6_endx_sids, node, nnode, sra))
		srv6_endx_sid_del(sra);

	return 0;
}

/**
 * When IS-IS Adjacency got one or more IPv6 addresses, add new
 * IPv6 address to corresponding SRv6 End.X SID accordingly.
 *
 * @param adj	  IS-IS Adjacency
 * @param family  Inet Family (IPv4 or IPv6)
 * @param global  Indicate if it concerns the Local or Global IPv6 addresses
 *
 * @return	  0
 */
static int srv6_adj_ip_enabled(struct isis_adjacency *adj, int family,
			       bool global)
{
	if (!adj->circuit->area->srv6db.config.enabled || global ||
	    family != AF_INET6)
		return 0;

	srv6_endx_sid_add(adj);

	return 0;
}

/**
 * When IS-IS Adjacency doesn't have any IPv6 addresses anymore,
 * delete the corresponding SRv6 End.X SID(s) accordingly.
 *
 * @param adj	  IS-IS Adjacency
 * @param family  Inet Family (IPv4 or IPv6)
 * @param global  Indicate if it concerns the Local or Global IPv6 addresses
 *
 * @return	  0
 */
static int srv6_adj_ip_disabled(struct isis_adjacency *adj, int family,
				bool global)
{
	struct srv6_adjacency *sra;
	struct listnode *node, *nnode;

	if (!adj->circuit->area->srv6db.config.enabled || global ||
	    family != AF_INET6)
		return 0;

	for (ALL_LIST_ELEMENTS(adj->srv6_endx_sids, node, nnode, sra))
		srv6_endx_sid_del(sra);

	return 0;
}

/**
 * Show Segment Routing over IPv6 (SRv6) Node.
 *
 * @param vty	VTY output
 * @param area	IS-IS area
 * @param level	IS-IS level
 */
static void show_node(struct vty *vty, struct isis_area *area, int level)
{
	struct isis_lsp *lsp;
	struct ttable *tt;

	vty_out(vty, " IS-IS %s SRv6-Nodes:\n\n", circuit_t2string(level));

	/* Prepare table. */
	tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]);
	ttable_add_row(
		tt,
		"System ID|Algorithm|SRH Max SL|SRH Max End Pop|SRH Max H.encaps|SRH Max End D");
	tt->style.cell.rpad = 2;
	tt->style.corner = '+';
	ttable_restyle(tt);
	ttable_rowseps(tt, 0, BOTTOM, true, '-');

	frr_each (lspdb, &area->lspdb[level - 1], lsp) {
		struct isis_router_cap *cap;

		if (!lsp->tlvs)
			continue;
		cap = lsp->tlvs->router_cap;
		if (!cap)
			continue;

		ttable_add_row(tt, "%pSY|%s|%u|%u|%u|%u", lsp->hdr.lsp_id,
			       cap->algo[0] == SR_ALGORITHM_SPF ? "SPF"
								: "S-SPF",
			       cap->srv6_msd.max_seg_left_msd,
			       cap->srv6_msd.max_end_pop_msd,
			       cap->srv6_msd.max_h_encaps_msd,
			       cap->srv6_msd.max_end_d_msd);
	}

	/* Dump the generated table. */
	if (tt->nrows > 1) {
		char *table;

		table = ttable_dump(tt, "\n");
		vty_out(vty, "%s\n", table);
		XFREE(MTYPE_TMP, table);
	}
	ttable_del(tt);
}

DEFUN(show_srv6_node, show_srv6_node_cmd,
      "show " PROTO_NAME " segment-routing srv6 node",
      SHOW_STR
      PROTO_HELP
      "Segment-Routing\n"
      "Segment-Routing over IPv6 (SRv6)\n"
      "SRv6 node\n")
{
	struct listnode *node, *inode;
	struct isis_area *area;
	struct isis *isis;

	for (ALL_LIST_ELEMENTS_RO(im->isis, inode, isis)) {
		for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) {
			vty_out(vty, "Area %s:\n",
				area->area_tag ? area->area_tag : "null");
			if (!area->srv6db.config.enabled) {
				vty_out(vty, " SRv6 is disabled\n");
				continue;
			}
			for (int level = ISIS_LEVEL1; level <= ISIS_LEVELS;
			     level++)
				show_node(vty, area, level);
		}
	}

	return CMD_SUCCESS;
}

int isis_srv6_ifp_up_notify(struct interface *ifp)
{
	struct isis *isis = isis_lookup_by_vrfid(VRF_DEFAULT);
	struct listnode *node, *node2;
	struct isis_area *area;
	struct isis_srv6_sid *sid;

	if (!isis)
		return 0;

	/* Walk through all areas of the ISIS instance */
	for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) {
		/* Skip area, if SRv6 is not enabled */
		if (!area->srv6db.config.enabled)
			continue;

		/* Skip area if the interface is not the one configured for SRv6 */
		if (strncmp(area->srv6db.config.srv6_ifname, ifp->name, IF_NAMESIZE))
			continue;

		sr_debug("Interface %s went up. Installing SIDs for area %s in data plane", ifp->name, area->area_tag);

		/* Walk through all SIDs and re-install them into the data plane with the newly configured interface */
		for (ALL_LIST_ELEMENTS_RO(area->srv6db.srv6_sids, node2, sid)) {
			sr_debug("Installing SID %pI6 from the data plane", &sid->sid);
			isis_zebra_srv6_sid_install(area, sid);
		}
	}

	return 0;
}

/**
 * IS-IS SRv6 initialization for given area.
 *
 * @param area	IS-IS area
 */
void isis_srv6_area_init(struct isis_area *area)
{
	struct isis_srv6_db *srv6db;

	if (!area)
		return;

	srv6db = &area->srv6db;

	sr_debug("ISIS-SRv6 (%s): Initialize Segment Routing SRv6 DB",
		 area->area_tag);

	/* Initialize SRv6 Data Base */
	memset(srv6db, 0, sizeof(*srv6db));
	srv6db->srv6_endx_sids = list_new();

	/* Pull defaults from the YANG module */
#ifndef FABRICD
	srv6db->config.enabled = yang_get_default_bool("%s/enabled", ISIS_SRV6);
	srv6db->config.max_seg_left_msd =
		yang_get_default_uint8("%s/msd/node-msd/max-segs-left",
				       ISIS_SRV6);
	srv6db->config.max_end_pop_msd =
		yang_get_default_uint8("%s/msd/node-msd/max-end-pop", ISIS_SRV6);
	srv6db->config.max_h_encaps_msd =
		yang_get_default_uint8("%s/msd/node-msd/max-h-encaps",
				       ISIS_SRV6);
	srv6db->config.max_end_d_msd =
		yang_get_default_uint8("%s/msd/node-msd/max-end-d", ISIS_SRV6);
	strlcpy(srv6db->config.srv6_ifname, yang_get_default_string("%s/interface", ISIS_SRV6), sizeof(srv6db->config.srv6_ifname));
#else
	srv6db->config.enabled = false;
	srv6db->config.max_seg_left_msd = ISIS_DEFAULT_SRV6_MAX_SEG_LEFT_MSD;
	srv6db->config.max_end_pop_msd = ISIS_DEFAULT_SRV6_MAX_END_POP_MSD;
	srv6db->config.max_h_encaps_msd = ISIS_DEFAULT_SRV6_MAX_H_ENCAPS_MSD;
	srv6db->config.max_end_d_msd = ISIS_DEFAULT_SRV6_MAX_END_D_MSD;
	strlcpy(srv6db->config.srv6_ifname, ISIS_DEFAULT_SRV6_IFNAME, sizeof(srv6db->config.srv6_ifname));
#endif

	/* Initialize SRv6 Locator chunks list */
	srv6db->srv6_locator_chunks = list_new();

	/* Initialize SRv6 SIDs list */
	srv6db->srv6_sids = list_new();
	srv6db->srv6_sids->del = (void (*)(void *))isis_srv6_sid_free;
}

/**
 * Terminate IS-IS SRv6 for the given area.
 *
 * @param area	IS-IS area
 */
void isis_srv6_area_term(struct isis_area *area)
{
	struct isis_srv6_db *srv6db = &area->srv6db;
	struct srv6_adjacency *sra;
	struct listnode *node, *nnode;
	struct srv6_locator_chunk *chunk;

	sr_debug("ISIS-SRv6 (%s): Terminate SRv6", area->area_tag);

	/* Uninstall all local SRv6 End.X SIDs */
	if (area->srv6db.config.enabled)
		for (ALL_LIST_ELEMENTS(area->srv6db.srv6_endx_sids, node, nnode,
				       sra))
			srv6_endx_sid_del(sra);

	/* Free SRv6 Locator chunks list */
	for (ALL_LIST_ELEMENTS(srv6db->srv6_locator_chunks, node, nnode, chunk))
		srv6_locator_chunk_free(&chunk);
	list_delete(&srv6db->srv6_locator_chunks);

	/* Free SRv6 SIDs list */
	list_delete(&srv6db->srv6_sids);
	list_delete(&srv6db->srv6_endx_sids);
}

/**
 * IS-IS SRv6 global initialization.
 */
void isis_srv6_init(void)
{
	install_element(VIEW_NODE, &show_srv6_node_cmd);

	/* Register hooks. */
	hook_register(isis_adj_state_change_hook, srv6_adj_state_change);
	hook_register(isis_adj_ip_enabled_hook, srv6_adj_ip_enabled);
	hook_register(isis_adj_ip_disabled_hook, srv6_adj_ip_disabled);
}

/**
 * IS-IS SRv6 global terminate.
 */
void isis_srv6_term(void)
{
	/* Unregister hooks. */
	hook_unregister(isis_adj_state_change_hook, srv6_adj_state_change);
	hook_unregister(isis_adj_ip_enabled_hook, srv6_adj_ip_enabled);
	hook_unregister(isis_adj_ip_disabled_hook, srv6_adj_ip_disabled);
}