summaryrefslogtreecommitdiffstats
path: root/isisd/isis_srv6.c
blob: a26552d4e9b31d8ee5b42614a735719b8e77d029 (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
// 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 "isisd/isisd.h"
#include "isisd/isis_misc.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");

/**
 * 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;

	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);
	}

	/* 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;
}

/**
 * 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;

	for (ALL_LIST_ELEMENTS_RO(area->srv6db.srv6_sids, node, s))
		if (sid_same(&s->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);
}

/**
 * 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;
}

/**
 * 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));

	/* Pull defaults from the YANG module */
	srv6db->config.enabled = yang_get_default_bool("%s/enabled", ISIS_SRV6);

	srv6db->config.max_seg_left_msd = SRV6_MAX_SEG_LEFT;
	srv6db->config.max_end_pop_msd = SRV6_MAX_END_POP;
	srv6db->config.max_h_encaps_msd = SRV6_MAX_H_ENCAPS;
	srv6db->config.max_end_d_msd = SRV6_MAX_END_D;

	/* 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 listnode *node, *nnode;
	struct srv6_locator_chunk *chunk;

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

	/* 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);
}

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

/**
 * IS-IS SRv6 global terminate.
 */
void isis_srv6_term(void)
{
}