diff options
-rw-r--r-- | isisd/isis_adjacency.c | 68 | ||||
-rw-r--r-- | isisd/isis_adjacency.h | 4 | ||||
-rw-r--r-- | isisd/isis_cli.c | 4 | ||||
-rw-r--r-- | isisd/isis_errors.c | 6 | ||||
-rw-r--r-- | isisd/isis_errors.h | 1 | ||||
-rw-r--r-- | isisd/isis_lsp.c | 66 | ||||
-rw-r--r-- | isisd/isis_main.c | 19 | ||||
-rw-r--r-- | isisd/isis_nb.c | 3 | ||||
-rw-r--r-- | isisd/isis_nb.h | 14 | ||||
-rw-r--r-- | isisd/isis_nb_config.c | 262 | ||||
-rw-r--r-- | isisd/isis_route.c | 3 | ||||
-rw-r--r-- | isisd/isis_route.h | 2 | ||||
-rw-r--r-- | isisd/isis_spf.c | 2 | ||||
-rw-r--r-- | isisd/isis_sr.c | 1525 | ||||
-rw-r--r-- | isisd/isis_sr.h | 259 | ||||
-rw-r--r-- | isisd/isis_tlvs.c | 35 | ||||
-rw-r--r-- | isisd/isis_tlvs.h | 9 | ||||
-rw-r--r-- | isisd/isis_zebra.c | 280 | ||||
-rw-r--r-- | isisd/isis_zebra.h | 16 | ||||
-rw-r--r-- | isisd/isisd.c | 43 | ||||
-rw-r--r-- | isisd/isisd.h | 8 | ||||
-rw-r--r-- | isisd/subdir.am | 3 | ||||
-rw-r--r-- | lib/mpls.h | 3 | ||||
-rw-r--r-- | zebra/zebra_mpls.h | 7 |
24 files changed, 2531 insertions, 111 deletions
diff --git a/isisd/isis_adjacency.c b/isisd/isis_adjacency.c index 4e0ee4448..acfe3e2e1 100644 --- a/isisd/isis_adjacency.c +++ b/isisd/isis_adjacency.c @@ -93,6 +93,7 @@ struct isis_adjacency *isis_new_adj(const uint8_t *id, const uint8_t *snpa, .last_dis_change = time(NULL); } } + adj->adj_sids = list_new(); return adj; } @@ -122,6 +123,44 @@ struct isis_adjacency *isis_adj_lookup_snpa(const uint8_t *ssnpa, return NULL; } +bool isis_adj_exists(const struct isis_area *area, int level, + const uint8_t *sysid) +{ + struct isis_circuit *circuit; + struct listnode *node; + + for (ALL_LIST_ELEMENTS_RO(area->circuit_list, node, circuit)) { + struct isis_adjacency *adj; + struct listnode *anode; + struct list *adjdb; + + switch (circuit->circ_type) { + case CIRCUIT_T_BROADCAST: + adjdb = circuit->u.bc.adjdb[level - 1]; + if (!adjdb) + continue; + + for (ALL_LIST_ELEMENTS_RO(adjdb, anode, adj)) { + if (!memcmp(adj->sysid, sysid, ISIS_SYS_ID_LEN)) + return true; + } + break; + case CIRCUIT_T_P2P: + adj = circuit->u.p2p.neighbor; + if (!adj) + break; + + if (!memcmp(adj->sysid, sysid, ISIS_SYS_ID_LEN)) + return true; + break; + default: + break; + } + } + + return false; +} + DEFINE_HOOK(isis_adj_state_change_hook, (struct isis_adjacency *adj), (adj)) void isis_delete_adj(void *arg) @@ -145,6 +184,7 @@ void isis_delete_adj(void *arg) XFREE(MTYPE_ISIS_ADJACENCY_INFO, adj->ipv6_addresses); adj_mt_finish(adj); + list_delete(&adj->adj_sids); XFREE(MTYPE_ISIS_ADJACENCY, adj); return; @@ -441,6 +481,9 @@ void isis_adj_print_vty(struct isis_adjacency *adj, struct vty *vty, } if (detail == ISIS_UI_LEVEL_DETAIL) { + struct sr_adjacency *sra; + struct listnode *anode; + level = adj->level; vty_out(vty, "\n"); if (adj->circuit) @@ -529,6 +572,31 @@ void isis_adj_print_vty(struct isis_adjacency *adj, struct vty *vty, vty_out(vty, " %s\n", buf); } } + for (ALL_LIST_ELEMENTS_RO(adj->adj_sids, anode, sra)) { + const char *adj_type; + const char *backup; + uint32_t sid; + + switch (sra->adj->circuit->circ_type) { + case CIRCUIT_T_BROADCAST: + adj_type = "LAN Adjacency-SID"; + sid = sra->u.ladj_sid->sid; + break; + case CIRCUIT_T_P2P: + adj_type = "Adjacency-SID"; + sid = sra->u.adj_sid->sid; + break; + default: + continue; + } + backup = (sra->type == ISIS_SR_LAN_BACKUP) ? " (backup)" + : ""; + + vty_out(vty, " %s %s%s: %u\n", + (sra->nexthop.family == AF_INET) ? "IPv4" + : "IPv6", + adj_type, backup, sid); + } vty_out(vty, "\n"); } return; diff --git a/isisd/isis_adjacency.h b/isisd/isis_adjacency.h index 834eba792..ec17446ad 100644 --- a/isisd/isis_adjacency.h +++ b/isisd/isis_adjacency.h @@ -69,6 +69,7 @@ struct isis_dis_record { }; struct bfd_session; +struct isis_area; struct isis_adjacency { uint8_t snpa[ETH_ALEN]; /* NeighbourSNPAAddress */ @@ -103,6 +104,7 @@ struct isis_adjacency { uint16_t *mt_set; /* Topologies this adjacency is valid for */ unsigned int mt_count; /* Number of entries in mt_set */ struct bfd_session *bfd_session; + struct list *adj_sids; /* Segment Routing Adj-SIDs. */ }; struct isis_threeway_adj; @@ -111,6 +113,8 @@ struct isis_adjacency *isis_adj_lookup(const uint8_t *sysid, struct list *adjdb); struct isis_adjacency *isis_adj_lookup_snpa(const uint8_t *ssnpa, struct list *adjdb); +bool isis_adj_exists(const struct isis_area *area, int level, + const uint8_t *sysid); struct isis_adjacency *isis_new_adj(const uint8_t *id, const uint8_t *snpa, int level, struct isis_circuit *circuit); void isis_delete_adj(void *adj); diff --git a/isisd/isis_cli.c b/isisd/isis_cli.c index 0101e30ad..c421750a8 100644 --- a/isisd/isis_cli.c +++ b/isisd/isis_cli.c @@ -1480,7 +1480,7 @@ DEFPY (isis_sr_prefix_sid, isis_sr_prefix_sid_cmd, "segment-routing prefix\ <A.B.C.D/M|X:X::X:X/M>$prefix\ - <absolute$sid_type (16000-1048575)$sid_value|index$sid_type (0-65535)$sid_value>\ + <absolute$sid_type (16-1048575)$sid_value|index$sid_type (0-65535)$sid_value>\ [<no-php-flag|explicit-null>$lh_behavior]", SR_STR "Prefix SID\n" @@ -1518,7 +1518,7 @@ DEFPY (isis_sr_prefix_sid, DEFPY (no_isis_sr_prefix_sid, no_isis_sr_prefix_sid_cmd, "no segment-routing prefix <A.B.C.D/M|X:X::X:X/M>$prefix\ - [<absolute$sid_type (16000-1048575)|index (0-65535)> [<no-php-flag|explicit-null>]]", + [<absolute$sid_type (16-1048575)|index (0-65535)> [<no-php-flag|explicit-null>]]", NO_STR SR_STR "Prefix SID\n" diff --git a/isisd/isis_errors.c b/isisd/isis_errors.c index 755e70dbf..7530d0b96 100644 --- a/isisd/isis_errors.c +++ b/isisd/isis_errors.c @@ -38,6 +38,12 @@ static struct log_ref ferr_isis_err[] = { .suggestion = "Ensure configuration is correct" }, { + .code = EC_ISIS_SID_OVERFLOW, + .title = "SID index overflow", + .description = "Isis has detected that a SID index falls outside of its associated SRGB range", + .suggestion = "Configure a larger SRGB" + }, + { .code = END_FERR, } }; diff --git a/isisd/isis_errors.h b/isisd/isis_errors.h index 073273760..d5674dbf3 100644 --- a/isisd/isis_errors.h +++ b/isisd/isis_errors.h @@ -26,6 +26,7 @@ enum isis_log_refs { EC_ISIS_PACKET = ISIS_FERR_START, EC_ISIS_CONFIG, + EC_ISIS_SID_OVERFLOW, }; extern void isis_error_init(void); diff --git a/isisd/isis_lsp.c b/isisd/isis_lsp.c index effd19ed7..e578f616f 100644 --- a/isisd/isis_lsp.c +++ b/isisd/isis_lsp.c @@ -55,6 +55,7 @@ #include "isisd/isis_mt.h" #include "isisd/isis_tlvs.h" #include "isisd/isis_te.h" +#include "isisd/isis_sr.h" #include "isisd/fabricd.h" #include "isisd/isis_tx_queue.h" #include "isisd/isis_nb.h" @@ -763,9 +764,15 @@ static void lsp_build_ext_reach_ipv4(struct isis_lsp *lsp, if (area->oldmetric) isis_tlvs_add_oldstyle_ip_reach(lsp->tlvs, ipv4, metric); - if (area->newmetric) - isis_tlvs_add_extended_ip_reach(lsp->tlvs, ipv4, - metric); + if (area->newmetric) { + struct sr_prefix_cfg *pcfg = NULL; + + if (area->srdb.enabled) + pcfg = isis_sr_cfg_prefix_find(area, ipv4); + + isis_tlvs_add_extended_ip_reach(lsp->tlvs, ipv4, metric, + true, pcfg); + } } } @@ -792,9 +799,14 @@ static void lsp_build_ext_reach_ipv6(struct isis_lsp *lsp, metric = MAX_WIDE_PATH_METRIC; if (!src_p || !src_p->prefixlen) { + struct sr_prefix_cfg *pcfg = NULL; + + if (area->srdb.enabled) + pcfg = isis_sr_cfg_prefix_find(area, p); + isis_tlvs_add_ipv6_reach(lsp->tlvs, isis_area_ipv6_topology(area), - p, metric); + p, metric, true, pcfg); } else if (isis_area_ipv6_dstsrc_enabled(area)) { isis_tlvs_add_ipv6_dstsrc_reach(lsp->tlvs, ISIS_MT_IPV6_DSTSRC, @@ -910,6 +922,33 @@ static void lsp_build(struct isis_lsp *lsp, struct isis_area *area) area->area_tag); } + /* Add Router Capability TLV. */ + if (isis->router_id != 0) { + struct isis_router_cap cap = {}; + + cap.router_id.s_addr = isis->router_id; + + /* Add SR Sub-TLVs if SR is enabled. */ + if (area->srdb.enabled) { + struct isis_sr_db *srdb = &area->srdb; + uint32_t range_size; + + range_size = srdb->config.srgb_upper_bound + - srdb->config.srgb_lower_bound + 1; + cap.srgb.flags = ISIS_SUBTLV_SRGB_FLAG_I + | ISIS_SUBTLV_SRGB_FLAG_V; + cap.srgb.range_size = range_size; + cap.srgb.lower_bound = srdb->config.srgb_lower_bound; + cap.algo[0] = SR_ALGORITHM_SPF; + cap.algo[1] = SR_ALGORITHM_UNSET; + cap.msd = srdb->config.msd; + } + + isis_tlvs_set_router_capability(lsp->tlvs, &cap); + lsp_debug("ISIS (%s): Adding Router Capabilities information", + area->area_tag); + } + /* IPv4 address and TE router ID TLVs. * In case of the first one we don't follow "C" vendor, * but "J" vendor behavior - one IPv4 address is put @@ -996,13 +1035,21 @@ static void lsp_build(struct isis_lsp *lsp, struct isis_area *area) } if (area->newmetric) { + struct sr_prefix_cfg *pcfg = NULL; + lsp_debug( "ISIS (%s): Adding te-style IP reachability for %s", area->area_tag, prefix2str(ipv4, buf, sizeof(buf))); + + if (area->srdb.enabled) + pcfg = isis_sr_cfg_prefix_find( + area, ipv4); + isis_tlvs_add_extended_ip_reach( - lsp->tlvs, ipv4, metric); + lsp->tlvs, ipv4, metric, false, + pcfg); } } } @@ -1014,14 +1061,21 @@ static void lsp_build(struct isis_lsp *lsp, struct isis_area *area) for (ALL_LIST_ELEMENTS_RO(circuit->ipv6_non_link, ipnode, ipv6)) { + struct sr_prefix_cfg *pcfg = NULL; + lsp_debug( "ISIS (%s): Adding IPv6 reachability for %s", area->area_tag, prefix2str(ipv6, buf, sizeof(buf))); + + if (area->srdb.enabled) + pcfg = isis_sr_cfg_prefix_find(area, + ipv6); + isis_tlvs_add_ipv6_reach( lsp->tlvs, isis_area_ipv6_topology(area), ipv6, - metric); + metric, false, pcfg); } } diff --git a/isisd/isis_main.c b/isisd/isis_main.c index 4c841dffe..78654b2f1 100644 --- a/isisd/isis_main.c +++ b/isisd/isis_main.c @@ -83,7 +83,9 @@ struct zebra_privs_t isisd_privs = { .cap_num_i = 0}; /* isisd options */ -struct option longopts[] = {{0}}; +static const struct option longopts[] = { + {"int_num", required_argument, NULL, 'I'}, + {0}}; /* Master of threads. */ struct thread_master *master; @@ -99,6 +101,7 @@ void sigusr1(void); static __attribute__((__noreturn__)) void terminate(int i) { + isis_sr_term(); isis_zebra_stop(); exit(i); } @@ -196,13 +199,16 @@ FRR_DAEMON_INFO(isisd, ISIS, .vty_port = ISISD_VTY_PORT, int main(int argc, char **argv, char **envp) { int opt; + int instance = 1; #ifdef FABRICD frr_preinit(&fabricd_di, argc, argv); #else frr_preinit(&isisd_di, argc, argv); #endif - frr_opt_add("", longopts, ""); + frr_opt_add( + "I:", longopts, + " -I, --int_num Set instance number (label-manager)\n"); /* Command line argument treatment. */ while (1) { @@ -214,6 +220,12 @@ int main(int argc, char **argv, char **envp) switch (opt) { case 0: break; + case 'I': + instance = atoi(optarg); + if (instance < 1 || instance > (unsigned short)-1) + zlog_err("Instance %i out of range (1..%u)", + instance, (unsigned short)-1); + break; default: frr_help_exit(1); break; @@ -242,13 +254,14 @@ int main(int argc, char **argv, char **envp) isis_redist_init(); isis_route_map_init(); isis_mpls_te_init(); + isis_sr_init(); lsp_init(); mt_init(); /* create the global 'isis' instance */ isis_new(1, VRF_DEFAULT); - isis_zebra_init(master); + isis_zebra_init(master, instance); isis_bfd_init(); fabricd_init(); diff --git a/isisd/isis_nb.c b/isisd/isis_nb.c index 2dedebf98..8e976d9bc 100644 --- a/isisd/isis_nb.c +++ b/isisd/isis_nb.c @@ -464,6 +464,7 @@ const struct frr_yang_module_info frr_isisd_info = { { .xpath = "/frr-isisd:isis/instance/segment-routing/srgb", .cbs = { + .apply_finish = isis_instance_segment_routing_srgb_apply_finish, .cli_show = cli_show_isis_srgb, }, }, @@ -492,6 +493,8 @@ const struct frr_yang_module_info frr_isisd_info = { .cbs = { .create = isis_instance_segment_routing_prefix_sid_map_prefix_sid_create, .destroy = isis_instance_segment_routing_prefix_sid_map_prefix_sid_destroy, + .pre_validate = isis_instance_segment_routing_prefix_sid_map_prefix_sid_pre_validate, + .apply_finish = isis_instance_segment_routing_prefix_sid_map_prefix_sid_apply_finish, .cli_show = cli_show_isis_prefix_sid, }, }, diff --git a/isisd/isis_nb.h b/isisd/isis_nb.h index 19ebee0e0..58f6c3892 100644 --- a/isisd/isis_nb.h +++ b/isisd/isis_nb.h @@ -183,11 +183,11 @@ int isis_instance_segment_routing_srgb_upper_bound_modify( int isis_instance_segment_routing_msd_node_msd_modify( struct nb_cb_modify_args *args); int isis_instance_segment_routing_msd_node_msd_destroy( - struct nb_cb_modify_args *args); + struct nb_cb_destroy_args *args); int isis_instance_segment_routing_prefix_sid_map_prefix_sid_create( - struct nb_cb_modify_args *args); + struct nb_cb_create_args *args); int isis_instance_segment_routing_prefix_sid_map_prefix_sid_destroy( - struct nb_cb_modify_args *args); + struct nb_cb_destroy_args *args); int isis_instance_segment_routing_prefix_sid_map_prefix_sid_sid_value_type_modify( struct nb_cb_modify_args *args); int isis_instance_segment_routing_prefix_sid_map_prefix_sid_sid_value_modify( @@ -279,6 +279,10 @@ struct yang_data * lib_interface_isis_event_counters_authentication_fails_get_elem( struct nb_cb_get_elem_args *args); +/* Optional 'pre_validate' callbacks. */ +int isis_instance_segment_routing_prefix_sid_map_prefix_sid_pre_validate( + struct nb_cb_pre_validate_args *args); + /* Optional 'apply_finish' callbacks. */ void ietf_backoff_delay_apply_finish(struct nb_cb_apply_finish_args *args); void area_password_apply_finish(struct nb_cb_apply_finish_args *args); @@ -291,6 +295,10 @@ void default_info_origin_ipv6_apply_finish( void redistribute_apply_finish(const struct lyd_node *dnode, int family); void redistribute_ipv4_apply_finish(struct nb_cb_apply_finish_args *args); void redistribute_ipv6_apply_finish(struct nb_cb_apply_finish_args *args); +void isis_instance_segment_routing_srgb_apply_finish( + struct nb_cb_apply_finish_args *args); +void isis_instance_segment_routing_prefix_sid_map_prefix_sid_apply_finish( + struct nb_cb_apply_finish_args *args); /* Optional 'cli_show' callbacks. */ void cli_show_router_isis(struct vty *vty, struct lyd_node *dnode, diff --git a/isisd/isis_nb_config.c b/isisd/isis_nb_config.c index f97202c9a..ec3b7baa3 100644 --- a/isisd/isis_nb_config.c +++ b/isisd/isis_nb_config.c @@ -1405,35 +1405,78 @@ int isis_instance_mpls_te_router_address_destroy( /* * XPath: /frr-isisd:isis/instance/segment-routing/enabled */ -int isis_instance_segment_routing_enabled_modify(enum nb_event event, - const struct lyd_node *dnode, - union nb_resource *resource) +int isis_instance_segment_routing_enabled_modify( + struct nb_cb_modify_args *args) { - switch (event) { - case NB_EV_VALIDATE: - case NB_EV_PREPARE: - case NB_EV_ABORT: - case NB_EV_APPLY: - /* TODO: implement me. */ - break; + struct isis_area *area; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + area = nb_running_get_entry(args->dnode, NULL, true); + area->srdb.config.enabled = yang_dnode_get_bool(args->dnode, NULL); + + if (area->srdb.config.enabled) { + if (IS_DEBUG_ISIS(DEBUG_EVENTS)) + zlog_debug("SR: Segment Routing: OFF -> ON"); + + if (isis_sr_start(area) == 0) + area->srdb.enabled = true; + } else { + if (IS_DEBUG_ISIS(DEBUG_EVENTS)) + zlog_debug("SR: Segment Routing: ON -> OFF"); + + isis_sr_stop(area); + area->srdb.enabled = false; } return NB_OK; } /* + * XPath: /frr-isisd:isis/instance/segment-routing/srgb + */ +void isis_instance_segment_routing_srgb_apply_finish( + struct nb_cb_apply_finish_args *args) +{ + struct isis_area *area; + uint32_t lower_bound, upper_bound; + int ret; + + area = nb_running_get_entry(args->dnode, NULL, true); + lower_bound = yang_dnode_get_uint32(args->dnode, "./lower-bound"); + upper_bound = yang_dnode_get_uint32(args->dnode, "./upper-bound"); + + ret = isis_sr_cfg_srgb_update(area, lower_bound, upper_bound); + if (area->srdb.config.enabled) { + if (ret == 0) + area->srdb.enabled = true; + else { + isis_sr_stop(area); + area->srdb.enabled = false; + } + } +} + +/* * XPath: /frr-isisd:isis/instance/segment-routing/srgb/lower-bound */ int isis_instance_segment_routing_srgb_lower_bound_modify( - enum nb_event event, const struct lyd_node *dnode, - union nb_resource *resource) + struct nb_cb_modify_args *args) { - switch (event) { + uint32_t lower_bound = yang_dnode_get_uint32(args->dnode, NULL); + + switch (args->event) { case NB_EV_VALIDATE: + if (!IS_MPLS_UNRESERVED_LABEL(lower_bound)) { + zlog_warn("Invalid SRGB lower bound: %" PRIu32, + lower_bound); + return NB_ERR_VALIDATION; + } + break; case NB_EV_PREPARE: case NB_EV_ABORT: case NB_EV_APPLY: - /* TODO: implement me. */ break; } @@ -1444,15 +1487,21 @@ int isis_instance_segment_routing_srgb_lower_bound_modify( * XPath: /frr-isisd:isis/instance/segment-routing/srgb/upper-bound */ int isis_instance_segment_routing_srgb_upper_bound_modify( - enum nb_event event, const struct lyd_node *dnode, - union nb_resource *resource) + struct nb_cb_modify_args *args) { - switch (event) { + uint32_t upper_bound = yang_dnode_get_uint32(args->dnode, NULL); + + switch (args->event) { case NB_EV_VALIDATE: + if (!IS_MPLS_UNRESERVED_LABEL(upper_bound)) { + zlog_warn("Invalid SRGB upper bound: %" PRIu32, + upper_bound); + return NB_ERR_VALIDATION; + } + break; case NB_EV_PREPARE: case NB_EV_ABORT: case NB_EV_APPLY: - /* TODO: implement me. */ break; } @@ -1463,32 +1512,31 @@ int isis_instance_segment_routing_srgb_upper_bound_modify( * XPath: /frr-isisd:isis/instance/segment-routing/msd/node-msd */ int isis_instance_segment_routing_msd_node_msd_modify( - enum nb_event event, const struct lyd_node *dnode, - union nb_resource *resource) + struct nb_cb_modify_args *args) { - switch (event) { - case NB_EV_VALIDATE: - case NB_EV_PREPARE: - case NB_EV_ABORT: - case NB_EV_APPLY: - /* TODO: implement me. */ - break; - } + struct isis_area *area; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + area = nb_running_get_entry(args->dnode, NULL, true); + area->srdb.config.msd = yang_dnode_get_uint8(args->dnode, NULL); + isis_sr_cfg_msd_update(area); return NB_OK; } int isis_instance_segment_routing_msd_node_msd_destroy( - enum nb_event event, const struct lyd_node *dnode) + struct nb_cb_destroy_args *args) { - switch (event) { - case NB_EV_VALIDATE: - case NB_EV_PREPARE: - case NB_EV_ABORT: - case NB_EV_APPLY: - /* TODO: implement me. */ - break; - } + struct isis_area *area; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + area = nb_running_get_entry(args->dnode, NULL, true); + area->srdb.config.msd = 0; + isis_sr_cfg_msd_update(area); return NB_OK; } @@ -1497,52 +1545,102 @@ int isis_instance_segment_routing_msd_node_msd_destroy( * XPath: /frr-isisd:isis/instance/segment-routing/prefix-sid-map/prefix-sid */ int isis_instance_segment_routing_prefix_sid_map_prefix_sid_create( - enum nb_event event, const struct lyd_node *dnode, - union nb_resource *resource) + struct nb_cb_create_args *args) { - switch (event) { - case NB_EV_VALIDATE: - case NB_EV_PREPARE: - case NB_EV_ABORT: - case NB_EV_APPLY: - /* TODO: implement me. */ - break; - } + struct isis_area *area; + struct prefix prefix; + struct sr_prefix_cfg *pcfg; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + area = nb_running_get_entry(args->dnode, NULL, true); + yang_dnode_get_prefix(&prefix, args->dnode, "./prefix"); + + pcfg = isis_sr_cfg_prefix_add(area, &prefix); + nb_running_set_entry(args->dnode, pcfg); return NB_OK; } int isis_instance_segment_routing_prefix_sid_map_prefix_sid_destroy( - enum nb_event event, const struct lyd_node *dnode) + struct nb_cb_destroy_args *args) { - switch (event) { - case NB_EV_VALIDATE: - case NB_EV_PREPARE: - case NB_EV_ABORT: - case NB_EV_APPLY: - /* TODO: implement me. */ + struct sr_prefix_cfg *pcfg; + struct isis_area *area; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + pcfg = nb_running_unset_entry(args->dnode); + area = pcfg->area; + isis_sr_cfg_prefix_del(pcfg); + lsp_regenerate_schedule(area, area->is_type, 0); + + return NB_OK; +} + +int isis_instance_segment_routing_prefix_sid_map_prefix_sid_pre_validate( + struct nb_cb_pre_validate_args *args) +{ + uint32_t srgb_lbound; + uint32_t srgb_ubound; + uint32_t srgb_range; + uint32_t sid; + enum sr_sid_value_type sid_type; + + srgb_lbound = yang_dnode_get_uint32(args->dnode, + "../../srgb/lower-bound"); + srgb_ubound = yang_dnode_get_uint32(args->dnode, + "../../srgb/upper-bound"); + sid = yang_dnode_get_uint32(args->dnode, "./sid-value"); + sid_type = yang_dnode_get_enum(args->dnode, "./sid-value-type"); + + srgb_range = srgb_ubound - srgb_lbound + 1; + switch (sid_type) { + case SR_SID_VALUE_TYPE_INDEX: + if (sid >= srgb_range) { + zlog_warn("SID index %u falls outside local SRGB range", + sid); + return NB_ERR_VALIDATION; + } + break; + case SR_SID_VALUE_TYPE_ABSOLUTE: + if (!IS_MPLS_UNRESERVED_LABEL(sid)) { + zlog_warn("Invalid absolute SID %u", sid); + return NB_ERR_VALIDATION; + } break; } return NB_OK; } +void isis_instance_segment_routing_prefix_sid_map_prefix_sid_apply_finish( + struct nb_cb_apply_finish_args *args) +{ + struct sr_prefix_cfg *pcfg; + struct isis_area *area; + + pcfg = nb_running_get_entry(args->dnode, NULL, true); + area = pcfg->area; + lsp_regenerate_schedule(area, area->is_type, 0); +} + /* * XPath: * /frr-isisd:isis/instance/segment-routing/prefix-sid-map/prefix-sid/sid-value-type */ int isis_instance_segment_routing_prefix_sid_map_prefix_sid_sid_value_type_modify( - enum nb_event event, const struct lyd_node *dnode, - union nb_resource *resource) + struct nb_cb_modify_args *args) { - switch (event) { - case NB_EV_VALIDATE: - case NB_EV_PREPARE: - case NB_EV_ABORT: - case NB_EV_APPLY: - /* TODO: implement me. */ - break; - } + struct sr_prefix_cfg *pcfg; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + pcfg = nb_running_get_entry(args->dnode, NULL, true); + pcfg->sid_type = yang_dnode_get_enum(args->dnode, NULL); return NB_OK; } @@ -1552,17 +1650,15 @@ int isis_instance_segment_routing_prefix_sid_map_prefix_sid_sid_value_type_modif * /frr-isisd:isis/instance/segment-routing/prefix-sid-map/prefix-sid/sid-value */ int isis_instance_segment_routing_prefix_sid_map_prefix_sid_sid_value_modify( - enum nb_event event, const struct lyd_node *dnode, - union nb_resource *resource) + struct nb_cb_modify_args *args) { - switch (event) { - case NB_EV_VALIDATE: - case NB_EV_PREPARE: - case NB_EV_ABORT: - case NB_EV_APPLY: - /* TODO: implement me. */ - break; - } + struct sr_prefix_cfg *pcfg; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + pcfg = nb_running_get_entry(args->dnode, NULL, true); + pcfg->sid = yang_dnode_get_uint32(args->dnode, NULL); return NB_OK; } @@ -1572,17 +1668,15 @@ int isis_instance_segment_routing_prefix_sid_map_prefix_sid_sid_value_modify( * /frr-isisd:isis/instance/segment-routing/prefix-sid-map/prefix-sid/last-hop-behavior */ int isis_instance_segment_routing_prefix_sid_map_prefix_sid_last_hop_behavior_modify( - enum nb_event event, const struct lyd_node *dnode, - union nb_resource *resource) + struct nb_cb_modify_args *args) { - switch (event) { - case NB_EV_VALIDATE: - case NB_EV_PREPARE: - case NB_EV_ABORT: - case NB_EV_APPLY: - /* TODO: implement me. */ - break; - } + struct sr_prefix_cfg *pcfg; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + pcfg = nb_running_get_entry(args->dnode, NULL, true); + pcfg->last_hop_behavior = yang_dnode_get_enum(args->dnode, NULL); return NB_OK; } diff --git a/isisd/isis_route.c b/isisd/isis_route.c index 516e3f90b..fa6af6c21 100644 --- a/isisd/isis_route.c +++ b/isisd/isis_route.c @@ -70,6 +70,7 @@ static struct isis_nexthop *isis_nexthop_create(int family, union g_addr *ip, nexthop->family = family; nexthop->ifindex = ifindex; nexthop->ip = *ip; + isis_sr_nexthop_reset(&nexthop->sr); return nexthop; } @@ -129,6 +130,7 @@ static void adjinfo2nexthop(int family, struct list *nexthops, nh = isis_nexthop_create( AF_INET, &ip, adj->circuit->interface->ifindex); + memcpy(nh->sysid, adj->sysid, sizeof(nh->sysid)); listnode_add(nexthops, nh); break; } @@ -143,6 +145,7 @@ static void adjinfo2nexthop(int family, struct list *nexthops, nh = isis_nexthop_create( AF_INET6, &ip, adj->circuit->interface->ifindex); + memcpy(nh->sysid, adj->sysid, sizeof(nh->sysid)); listnode_add(nexthops, nh); break; } diff --git a/isisd/isis_route.h b/isisd/isis_route.h index 96bcdc350..0356668d7 100644 --- a/isisd/isis_route.h +++ b/isisd/isis_route.h @@ -31,6 +31,8 @@ struct isis_nexthop { ifindex_t ifindex; int family; union g_addr ip; + uint8_t sysid[ISIS_SYS_ID_LEN]; + struct sr_nexthop_info sr; }; struct isis_route_info { diff --git a/isisd/isis_spf.c b/isisd/isis_spf.c index 30b9f8861..3091650ef 100644 --- a/isisd/isis_spf.c +++ b/isisd/isis_spf.c @@ -1214,6 +1214,8 @@ static int isis_run_spf_cb(struct thread *thread) isis_area_verify_routes(area); + isis_area_verify_sr(area); + /* walk all circuits and reset any spf specific flags */ struct listnode *node; struct isis_circuit *circuit; diff --git a/isisd/isis_sr.c b/isisd/isis_sr.c new file mode 100644 index 000000000..54689ea48 --- /dev/null +++ b/isisd/isis_sr.c @@ -0,0 +1,1525 @@ +/* + * This is an implementation of Segment Routing for IS-IS + * as per draft draft-ietf-isis-segment-routing-extensions-25 + * + * Copyright (C) 2019 Orange Labs http://www.orange.com + * + * Author: Olivier Dugeon <olivier.dugeon@orange.com> + * Contributor: Renato Westphal <renato@opensourcerouting.org> for NetDEF + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; see the file COPYING; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <zebra.h> + +#include "if.h" +#include "linklist.h" +#include "log.h" +#include "command.h" +#include "termtable.h" +#include "memory.h" +#include "prefix.h" +#include "table.h" +#include "vty.h" +#include "zclient.h" +#include "lib/lib_errors.h" + +#include "isisd/isisd.h" +#include "isisd/isis_spf.h" +#include "isisd/isis_spf_private.h" +#include "isisd/isis_adjacency.h" +#include "isisd/isis_route.h" +#include "isisd/isis_mt.h" +#include "isisd/isis_sr.h" +#include "isisd/isis_tlvs.h" +#include "isisd/isis_misc.h" +#include "isisd/isis_zebra.h" +#include "isisd/isis_errors.h" + +DEFINE_MTYPE_STATIC(ISISD, ISIS_SR_INFO, "ISIS segment routing information") + +static void isis_sr_prefix_uninstall(struct sr_prefix *srp); +static void isis_sr_prefix_reinstall(struct sr_prefix *srp, + bool replace_semantics); + +/*----------------------------------------------------------------------------*/ + +static inline int sr_prefix_sid_compare(const struct sr_prefix *a, + const struct sr_prefix *b) +{ + return prefix_cmp(&a->prefix, &b->prefix); +} +DECLARE_RBTREE_UNIQ(tree_sr_node_prefix, struct sr_prefix, node_entry, + sr_prefix_sid_compare) +DECLARE_RBTREE_UNIQ(tree_sr_area_prefix, struct sr_prefix, area_entry, + sr_prefix_sid_compare) + +static inline int sr_prefix_sid_cfg_compare(const struct sr_prefix_cfg *a, + const struct sr_prefix_cfg *b) +{ + return prefix_cmp(&a->prefix, &b->prefix); +} +DECLARE_RBTREE_UNIQ(tree_sr_prefix_cfg, struct sr_prefix_cfg, entry, + sr_prefix_sid_cfg_compare) + +static inline int sr_node_compare(const struct sr_node *a, + const struct sr_node *b) +{ + return memcmp(a->sysid, b->sysid, ISIS_SYS_ID_LEN); +} +DECLARE_RBTREE_UNIQ(tree_sr_node, struct sr_node, entry, sr_node_compare) + +/*----------------------------------------------------------------------------*/ + +/* Returns true if the interface/address pair corresponds to a Node-SID. */ +static bool isis_sr_prefix_is_node_sid(const struct interface *ifp, + const struct prefix *prefix) +{ + return (if_is_loopback(ifp) && is_host_route(prefix)); +} + +/* Handle changes in the local SRGB configuration. */ +int isis_sr_cfg_srgb_update(struct isis_area *area, uint32_t lower_bound, + uint32_t upper_bound) +{ + struct isis_sr_db *srdb = &area->srdb; + + /* First release the old SRGB. */ + if (srdb->config.enabled) + isis_zebra_release_label_range(srdb->config.srgb_lower_bound, + srdb->config.srgb_upper_bound); + + srdb->config.srgb_lower_bound = lower_bound; + srdb->config.srgb_upper_bound = upper_bound; + + if (srdb->enabled) { + struct sr_prefix *srp; + + /* Request new SRGB if SR is enabled. */ + if (isis_zebra_request_label_range( + srdb->config.srgb_lower_bound, + srdb->config.srgb_upper_bound + - srdb->config.srgb_lower_bound + 1)) + return -1; + + /* Reinstall local Prefix-SIDs to update their input labels. */ + for (int level = ISIS_LEVEL1; level <= ISIS_LEVELS; level++) { + frr_each (tree_sr_area_prefix, + &area->srdb.prefix_sids[level - 1], srp) { + isis_sr_prefix_reinstall(srp, false); + } + } + + lsp_regenerate_schedule(area, area->is_type, 0); + } else if (srdb->config.enabled) { + /* Try to enable SR again using the new SRGB. */ + if (isis_sr_start(area) == 0) + area->srdb.enabled = true; + } + + return 0; +} + +/* Handle changes in the local MSD configuration. */ +void isis_sr_cfg_msd_update(struct isis_area *area) +{ + lsp_regenerate_schedule(area, area->is_type, 0); +} + +/* Handle new Prefix-SID configuration. */ +struct sr_prefix_cfg *isis_sr_cfg_prefix_add(struct isis_area *area, + const struct prefix *prefix) +{ + struct sr_prefix_cfg *pcfg; + struct interface *ifp; + + pcfg = XCALLOC(MTYPE_ISIS_SR_INFO, sizeof(*pcfg)); + pcfg->prefix = *prefix; + pcfg->area = area; + + /* Pull defaults from the YANG module. */ + pcfg->sid_type = yang_get_default_enum( + "%s/prefix-sid-map/prefix-sid/sid-value-type", ISIS_SR); + pcfg->last_hop_behavior = yang_get_default_enum( + "%s/prefix-sid-map/prefix-sid/last-hop-behavior", ISIS_SR); + + /* Set the N-flag when appropriate. */ + ifp = if_lookup_prefix(prefix, VRF_DEFAULT); + if (ifp && isis_sr_prefix_is_node_sid(ifp, prefix)) + pcfg->node_sid = true; + + /* Save prefix-sid configuration. */ + tree_sr_prefix_cfg_add(&area->srdb.config.prefix_sids, pcfg); + + return pcfg; +} + +/* Handle removal of locally configured Prefix-SID. */ +void isis_sr_cfg_prefix_del(struct sr_prefix_cfg *pcfg) +{ + struct isis_area *area; + + area = pcfg->area; + tree_sr_prefix_cfg_del(&area->srdb.config.prefix_sids, pcfg); + XFREE(MTYPE_ISIS_SR_INFO, pcfg); +} + +/* Lookup Prefix-SID in the local configuration. */ +struct sr_prefix_cfg *isis_sr_cfg_prefix_find(struct isis_area *area, + union prefixconstptr prefix) +{ + struct sr_prefix_cfg pcfg = {}; + + prefix_copy(&pcfg.prefix, prefix.p); + return tree_sr_prefix_cfg_find(&area->srdb.config.prefix_sids, &pcfg); +} + +/* Fill in Prefix-SID Sub-TLV according to the corresponding configuration. */ +void isis_sr_prefix_cfg2subtlv(const struct sr_prefix_cfg *pcfg, bool external, + struct isis_prefix_sid *psid) +{ + /* Set SID algorithm. */ + psid->algorithm = SR_ALGORITHM_SPF; + + /* Set SID flags. */ + psid->flags = 0; + switch (pcfg->last_hop_behavior) { + case SR_LAST_HOP_BEHAVIOR_EXP_NULL: + SET_FLAG(psid->flags, ISIS_PREFIX_SID_NO_PHP); + SET_FLAG(psid->flags, ISIS_PREFIX_SID_EXPLICIT_NULL); + break; + case SR_LAST_HOP_BEHAVIOR_NO_PHP: + SET_FLAG(psid->flags, ISIS_PREFIX_SID_NO_PHP); + UNSET_FLAG(psid->flags, ISIS_PREFIX_SID_EXPLICIT_NULL); + break; + case SR_LAST_HOP_BEHAVIOR_PHP: + UNSET_FLAG(psid->flags, ISIS_PREFIX_SID_NO_PHP); + UNSET_FLAG(psid->flags, ISIS_PREFIX_SID_EXPLICIT_NULL); + break; + } + if (external) + SET_FLAG(psid->flags, ISIS_PREFIX_SID_READVERTISED); + if (pcfg->node_sid) + SET_FLAG(psid->flags, ISIS_PREFIX_SID_NODE); + + /* Set SID value. */ + psid->value = pcfg->sid; + if (pcfg->sid_type == SR_SID_VALUE_TYPE_ABSOLUTE) { + SET_FLAG(psid->flags, ISIS_PREFIX_SID_VALUE); + SET_FLAG(psid->flags, ISIS_PREFIX_SID_LOCAL); + } +} + +/*----------------------------------------------------------------------------*/ + +static struct sr_prefix *isis_sr_prefix_add(struct isis_area *area, + struct sr_node *srn, + union prefixconstptr prefix, + bool local, + const struct isis_prefix_sid *psid) +{ + struct sr_prefix *srp; + + srp = XCALLOC(MTYPE_ISIS_SR_INFO, sizeof(*srp)); + prefix_copy(&srp->prefix, prefix.p); + srp->sid = *psid; + srp->local_label = MPLS_INVALID_LABEL; + if (local) { + srp->type = ISIS_SR_PREFIX_LOCAL; + isis_sr_nexthop_reset(&srp->u.local.info); + } else { + srp->type = ISIS_SR_PREFIX_REMOTE; + srp->u.remote.rinfo = NULL; + } + srp->srn = srn; + tree_sr_node_prefix_add(&srn->prefix_sids, srp); + /* TODO: this might fail if we have Anycast SIDs in the IS-IS area. */ + tree_sr_area_prefix_add(&area->srdb.prefix_sids[srn->level - 1], srp); + + return srp; +} + +static void isis_sr_prefix_del(struct isis_area *area, struct sr_node *srn, + struct sr_prefix *srp) +{ + isis_sr_prefix_uninstall(srp); + tree_sr_node_prefix_del(&srn->prefix_sids, srp); + tree_sr_area_prefix_del(&area->srdb.prefix_sids[srn->level - 1], srp); + XFREE(MTYPE_ISIS_SR_INFO, srp); +} + +static struct sr_prefix *isis_sr_prefix_find_area(struct isis_area *area, + int level, + union prefixconstptr prefix) +{ + struct sr_prefix srp = {}; + + prefix_copy(&srp.prefix, prefix.p); + return tree_sr_area_prefix_find(&area->srdb.prefix_sids[level - 1], + &srp); +} + +static struct sr_prefix *isis_sr_prefix_find_node(struct sr_node *srn, + union prefixconstptr prefix) +{ + struct sr_prefix srp = {}; + + prefix_copy(&srp.prefix, prefix.p); + return tree_sr_node_prefix_find(&srn->prefix_sids, &srp); +} + +static struct sr_node *isis_sr_node_add(struct isis_area *area, int level, + const uint8_t *sysid, + const struct isis_router_cap *cap) +{ + struct sr_node *srn; + + srn = XCALLOC(MTYPE_ISIS_SR_INFO, sizeof(*srn)); + srn->level = level; + memcpy(srn->sysid, sysid, ISIS_SYS_ID_LEN); + srn->cap = *cap; + srn->area = area; + tree_sr_node_prefix_init(&srn->prefix_sids); + tree_sr_node_add(&area->srdb.sr_nodes[level - 1], srn); + + return srn; +} + +static void isis_sr_node_del(struct isis_area *area, int level, + struct sr_node *srn) +{ + /* Remove and uninstall Prefix-SIDs. */ + while (tree_sr_node_prefix_count(&srn->prefix_sids) > 0) { + struct sr_prefix *srp; + + srp = tree_sr_node_prefix_first(&srn->prefix_sids); + isis_sr_prefix_del(area, srn, srp); + } + + tree_sr_node_del(&area->srdb.sr_nodes[level - 1], srn); + XFREE(MTYPE_ISIS_SR_INFO, srn); +} + +static struct sr_node *isis_sr_node_find(struct isis_area *area, int level, + const uint8_t *sysid) +{ + struct sr_node srn = {}; + + memcpy(srn.sysid, sysid, ISIS_SYS_ID_LEN); + return tree_sr_node_find(&area->srdb.sr_nodes[level - 1], &srn); +} + +static void isis_sr_adj_srgb_update(struct isis_area *area, uint8_t *sysid, + int level) +{ + struct sr_prefix *srp; + + frr_each (tree_sr_area_prefix, &area->srdb.prefix_sids[level - 1], + srp) { + struct listnode *node; + struct isis_nexthop *nh; + + if (srp->type == ISIS_SR_PREFIX_LOCAL) + continue; + + if (srp->u.remote.rinfo == NULL) + continue; + + for (ALL_LIST_ELEMENTS_RO(srp->u.remote.rinfo->nexthops, node, + nh)) { + if (memcmp(nh->sysid, sysid, ISIS_SYS_ID_LEN) != 0) + continue; + + /* + * Reinstall all Prefix-SID nexthops using route replace + * semantics. + */ + isis_sr_prefix_reinstall(srp, true); + break; + } + } +} + +void isis_sr_nexthop_update(struct sr_nexthop_info *srnh, mpls_label_t label) +{ + srnh->label = label; + if (srnh->uptime == 0) + srnh->uptime = time(NULL); +} + +void isis_sr_nexthop_reset(struct sr_nexthop_info *srnh) +{ + srnh->label = MPLS_INVALID_LABEL; + srnh->uptime = 0; +} + +/*----------------------------------------------------------------------------*/ + +/* Lookup IS-IS route in the SPT. */ +static struct isis_route_info * +isis_sr_prefix_lookup_route(struct isis_area *area, enum spf_tree_id tree_id, + struct sr_prefix *srp) +{ + struct route_node *rn; + int level = srp->srn->level; + + rn = route_node_lookup(area->spftree[tree_id][level - 1]->route_table, + &srp->prefix); + if (rn) { + route_unlock_node(rn); + if (rn->info) + return rn->info; + } + + return NULL; +} + +/* Calculate Prefix-SID input label. */ +static mpls_label_t isis_sr_prefix_in_label(const struct sr_prefix *srp) +{ + const struct sr_node *srn = srp->srn; + struct isis_area *area = srn->area; + + /* Absolute SID value. */ + if (CHECK_FLAG(srp->sid.flags, + ISIS_PREFIX_SID_VALUE | ISIS_PREFIX_SID_LOCAL)) + return srp->sid.value; + + /* Index SID value. */ + if (srp->sid.value >= (area->srdb.config.srgb_upper_bound + - area->srdb.config.srgb_lower_bound + 1)) { + flog_warn(EC_ISIS_SID_OVERFLOW, + "%s: SID index %u falls outside local SRGB range", + __func__, srp->sid.value); + return MPLS_INVALID_LABEL; + } + + return (area->srdb.config.srgb_lower_bound + srp->sid.value); +} + +/* Calculate Prefix-SID output label. */ +static mpls_label_t isis_sr_prefix_out_label(const struct sr_prefix *srp, + const struct sr_node *srn_nexthop, + const uint8_t *sysid) +{ + const struct sr_node *srn = srp->srn; + + /* Is the adjacency the last hop? */ + if (memcmp(sysid, srn->sysid, ISIS_SYS_ID_LEN) == 0) { + if (!CHECK_FLAG(srp->sid.flags, ISIS_PREFIX_SID_NO_PHP)) + return MPLS_LABEL_IMPLICIT_NULL; + + if (CHECK_FLAG(srp->sid.flags, ISIS_PREFIX_SID_EXPLICIT_NULL)) { + if (srp->prefix.family == AF_INET) + return MPLS_LABEL_IPV4_EXPLICIT_NULL; + else + return MPLS_LABEL_IPV6_EXPLICIT_NULL; + } + /* Fallthrough */ + } + + /* Absolute SID value. */ + if (CHECK_FLAG(srp->sid.flags, + ISIS_PREFIX_SID_VALUE | ISIS_PREFIX_SID_LOCAL)) { + /* + * V/L SIDs have local significance, so only adjacent routers + * can use them. + */ + if (srp->srn != srn_nexthop) + return MPLS_INVALID_LABEL; + return srp->sid.value; + } + + /* Index SID value. */ + if (srp->sid.value >= srn_nexthop->cap.srgb.range_size) { + flog_warn(EC_ISIS_SID_OVERFLOW, + "%s: SID index %u falls outside remote SRGB range", + __func__, srp->sid.value); + return MPLS_INVALID_LABEL; + } + + return (srn_nexthop->cap.srgb.lower_bound + srp->sid.value); +} + +/* Process local Prefix-SID and install it if possible. */ +static int isis_sr_prefix_install_local(struct sr_prefix *srp) +{ + const struct sr_node *srn = srp->srn; + struct isis_area *area = srn->area; + mpls_label_t local_label; + + /* + * No need to install LSP to local Prefix-SID unless the + * no-PHP option is configured. + */ + if (!CHECK_FLAG(srp->sid.flags, ISIS_PREFIX_SID_NO_PHP) + || CHECK_FLAG(srp->sid.flags, ISIS_PREFIX_SID_EXPLICIT_NULL)) + return -1; + + if (IS_DEBUG_ISIS(DEBUG_SR)) { + zlog_debug("ISIS-SR (%s) installing Prefix-SID %pFX %s %u (%s)", + area->area_tag, &srp->prefix, + CHECK_FLAG(srp->sid.flags, ISIS_PREFIX_SID_VALUE) + ? "label" + : "index", + srp->sid.value, circuit_t2string(srn->level)); + zlog_debug(" nexthop self"); + } + + /* Calculate local label. */ + local_label = isis_sr_prefix_in_label(srp); + if (local_label == MPLS_INVALID_LABEL) + return -1; + + /* Update internal state. */ + srp->local_label = local_label; + isis_sr_nexthop_update(&srp->u.local.info, MPLS_LABEL_IMPLICIT_NULL); + + /* Install Prefix-SID in the forwarding plane. */ + isis_zebra_install_prefix_sid(srp); + + return 0; +} + +/* Process remote Prefix-SID and install it if possible. */ +static int isis_sr_prefix_install_remote(struct sr_prefix *srp) +{ + const struct sr_node *srn = srp->srn; + struct isis_area *area = srn->area; + enum spf_tree_id tree_id; + struct listnode *node; + struct isis_nexthop *nexthop; + mpls_label_t local_label; + size_t nexthop_num = 0; + + /* Lookup associated IS-IS route. */ + tree_id = (srp->prefix.family == AF_INET) ? SPFTREE_IPV4 : SPFTREE_IPV6; + srp->u.remote.rinfo = isis_sr_prefix_lookup_route(area, tree_id, srp); + if (!srp->u.remote.rinfo) + /* SPF hasn't converged for this route yet. */ + return -1; + + if (IS_DEBUG_ISIS(DEBUG_SR)) + zlog_debug("ISIS-SR (%s) installing Prefix-SID %pFX %s %u (%s)", + area->area_tag, &srp->prefix, + CHECK_FLAG(srp->sid.flags, ISIS_PREFIX_SID_VALUE) + ? "label" + : "index", + srp->sid.value, circuit_t2string(srn->level)); + + /* Calculate local label. */ + local_label = isis_sr_prefix_in_label(srp); + if (local_label == MPLS_INVALID_LABEL) + return -1; + + for (ALL_LIST_ELEMENTS_RO(srp->u.remote.rinfo->nexthops, node, + nexthop)) { + struct sr_node *srn_nexthop; + mpls_label_t remote_label; + + /* Check if the nexthop advertised a SRGB. */ + srn_nexthop = + isis_sr_node_find(area, srn->level, nexthop->sysid); + if (!srn_nexthop) + goto next; + + /* + * Check if the nexthop can handle SR-MPLS encapsulated IPv4 or + * IPv6 packets. + */ + if ((nexthop->family == AF_INET + && !IS_SR_IPV4(srn_nexthop->cap.srgb)) + || (nexthop->family == AF_INET6 + && !IS_SR_IPV6(srn_nexthop->cap.srgb))) + goto next; + + remote_label = isis_sr_prefix_out_label(srp, srn_nexthop, + nexthop->sysid); + if (remote_label == MPLS_INVALID_LABEL) + goto next; + + if (IS_DEBUG_ISIS(DEBUG_SR)) { + static char buf[INET6_ADDRSTRLEN]; + + inet_ntop(nexthop->family, &nexthop->ip, buf, + sizeof(buf)); + zlog_debug(" nexthop %s label %u", buf, remote_label); + } + + isis_sr_nexthop_update(&nexthop->sr, remote_label); + nexthop_num++; + continue; + next: + isis_sr_nexthop_reset(&nexthop->sr); + } + if (nexthop_num == 0) { + if (IS_DEBUG_ISIS(DEBUG_SR)) + zlog_debug(" no valid nexthops"); + return -1; + } + + /* Update internal state. */ + srp->local_label = local_label; + + /* Install Prefix-SID in the forwarding plane. */ + isis_zebra_install_prefix_sid(srp); + + return 0; +} + +/* Process local or remote Prefix-SID and install it if possible. */ +static void isis_sr_prefix_install(struct sr_prefix *srp) +{ + const struct sr_node *srn = srp->srn; + struct isis_area *area = srn->area; + int ret; + + /* L1 routes are preferred over the L2 ones. */ + if (area->is_type == IS_LEVEL_1_AND_2) { + struct sr_prefix *srp_l1, *srp_l2; + + switch (srn->level) { + case ISIS_LEVEL1: + srp_l2 = isis_sr_prefix_find_area(area, ISIS_LEVEL2, + &srp->prefix); + if (srp_l2) + isis_sr_prefix_uninstall(srp_l2); + break; + case ISIS_LEVEL2: + srp_l1 = isis_sr_prefix_find_area(area, ISIS_LEVEL1, + &srp->prefix); + if (srp_l1) + return; + break; + default: + break; + } + } + + if (srp->type == ISIS_SR_PREFIX_LOCAL) + ret = isis_sr_prefix_install_local(srp); + else + ret = isis_sr_prefix_install_remote(srp); + if (ret != 0) + isis_sr_prefix_uninstall(srp); +} + +/* Uninstall local or remote Prefix-SID. */ +static void isis_sr_prefix_uninstall(struct sr_prefix *srp) +{ + const struct sr_node *srn = srp->srn; + struct listnode *node; + struct isis_nexthop *nexthop; + + if (srp->local_label == MPLS_INVALID_LABEL) + return; + + if (IS_DEBUG_ISIS(DEBUG_SR)) + zlog_debug( + "ISIS-SR (%s) uninstalling Prefix-SID %pFX %s %u (%s)", + srn->area->area_tag, &srp->prefix, + CHECK_FLAG(srp->sid.flags, ISIS_PREFIX_SID_VALUE) + ? "label" + : "index", + srp->sid.value, circuit_t2string(srn->level)); + + + /* Uninstall Prefix-SID from the forwarding plane. */ + isis_zebra_uninstall_prefix_sid(srp); + + /* Reset internal state. */ + srp->local_label = MPLS_INVALID_LABEL; + switch (srp->type) { + case ISIS_SR_PREFIX_LOCAL: + isis_sr_nexthop_reset(&srp->u.local.info); + break; + case ISIS_SR_PREFIX_REMOTE: + if (srp->u.remote.rinfo) { + for (ALL_LIST_ELEMENTS_RO(srp->u.remote.rinfo->nexthops, + node, nexthop)) + isis_sr_nexthop_reset(&nexthop->sr); + } + break; + } +} + +/* Reinstall local or remote Prefix-SID. */ +static void isis_sr_prefix_reinstall(struct sr_prefix *srp, + bool replace_semantics) +{ + /* + * Route replace semantics can be used only when we know for sure that + * the Prefix-SID input label hasn't changed. Otherwise we need to + * uninstall the Prefix-SID first using the old input label before + * reinstalling it. + */ + if (!replace_semantics) + isis_sr_prefix_uninstall(srp); + + isis_sr_prefix_install(srp); +} + +/*----------------------------------------------------------------------------*/ + +/* Parse all SR-related information from the given Router Capabilities TLV. */ +static struct sr_node * +isis_sr_parse_router_cap_tlv(struct isis_area *area, int level, + const uint8_t *sysid, + const struct isis_router_cap *router_cap) +{ + struct sr_node *srn; + + if (!router_cap || router_cap->srgb.range_size == 0) + return NULL; + + srn = isis_sr_node_find(area, level, sysid); + if (srn) { + if (memcmp(&srn->cap, router_cap, sizeof(srn->cap)) != 0) { + srn->cap = *router_cap; + SET_FLAG(srn->parse_flags, F_ISIS_SR_NODE_MODIFIED); + } else + SET_FLAG(srn->parse_flags, F_ISIS_SR_NODE_UNCHANGED); + } else { + srn = isis_sr_node_add(area, level, sysid, router_cap); + SET_FLAG(srn->parse_flags, F_ISIS_SR_NODE_NEW); + } + + return srn; +} + +/* Parse list of Prefix-SID Sub-TLVs. */ +static void isis_sr_parse_prefix_sid_subtlvs(struct sr_node *srn, + union prefixconstptr prefix, + bool local, + struct isis_item_list *prefix_sids) +{ + struct isis_area *area = srn->area; + struct isis_item *i; + + for (i = prefix_sids->head; i; i = i->next) { + struct isis_prefix_sid *psid = (struct isis_prefix_sid *)i; + struct sr_prefix *srp; + + if (psid->algorithm != SR_ALGORITHM_SPF) + continue; + + srp = isis_sr_prefix_find_node(srn, prefix); + if (srp) { + if (srp->sid.flags != psid->flags + || srp->sid.algorithm != psid->algorithm + || srp->sid.value != psid->value) { + srp->sid = *psid; + SET_FLAG(srp->parse_flags, + F_ISIS_SR_PREFIX_SID_MODIFIED); + } else + SET_FLAG(srp->parse_flags, + F_ISIS_SR_PREFIX_SID_UNCHANGED); + } else { + srp = isis_sr_prefix_add(area, srn, prefix, local, + psid); + SET_FLAG(srp->parse_flags, F_ISIS_SR_PREFIX_SID_NEW); + } + /* + * Stop the Prefix-SID iteration since we only support the SPF + * algorithm for now. + */ + break; + } +} + +/* Parse all SR-related information from the given LSP. */ +static void isis_sr_parse_lsp(struct isis_area *area, int level, + struct sr_node **srn, struct isis_lsp *lsp) +{ + struct isis_item_list *items; + struct isis_item *i; + bool local = lsp->own_lsp; + + if (lsp->hdr.seqno == 0) { + zlog_warn("%s: lsp with 0 seq_num - ignore", __func__); + return; + } + + /* Parse the Router Capability TLV. */ + if (*srn == NULL) { + *srn = isis_sr_parse_router_cap_tlv( + area, level, lsp->hdr.lsp_id, lsp->tlvs->router_cap); + if (!*srn) + return; + } + + /* Parse the Extended IP Reachability TLV. */ + items = &lsp->tlvs->extended_ip_reach; + for (i = items->head; i; i = i->next) { + struct isis_extended_ip_reach *ir; + + ir = (struct isis_extended_ip_reach *)i; + if (!ir->subtlvs) + continue; + + isis_sr_parse_prefix_sid_subtlvs(*srn, &ir->prefix, local, + &ir->subtlvs->prefix_sids); + } + + /* Parse Multi Topology Reachable IPv6 Prefixes TLV. */ + items = isis_lookup_mt_items(&lsp->tlvs->mt_ipv6_reach, + ISIS_MT_IPV6_UNICAST); + for (i = items ? items->head : NULL; i; i = i->next) { + struct isis_ipv6_reach *ir; + + ir = (struct isis_ipv6_reach *)i; + if (!ir->subtlvs) + continue; + + isis_sr_parse_prefix_sid_subtlvs(*srn, &ir->prefix, local, + &ir->subtlvs->prefix_sids); + } +} + +/* Parse all SR-related information from the entire LSPDB. */ +static void isis_sr_parse_lspdb(struct isis_area *area) +{ + struct isis_lsp *lsp; + + for (int level = ISIS_LEVEL1; level <= ISIS_LEVELS; level++) { + frr_each (lspdb, &area->lspdb[level - 1], lsp) { + struct isis_lsp *frag; + struct listnode *node; + struct sr_node *srn = NULL; + + if (LSP_PSEUDO_ID(lsp->hdr.lsp_id)) + continue; + if (!lsp->tlvs) + continue; + + isis_sr_parse_lsp(area, level, &srn, lsp); + for (ALL_LIST_ELEMENTS_RO(lsp->lspu.frags, node, frag)) + isis_sr_parse_lsp(area, level, &srn, frag); + } + } +} + +/* Process any new/deleted/modified Prefix-SID in the LSPDB. */ +static void isis_sr_process_prefix_changes(struct sr_node *srn, + struct sr_prefix *srp) +{ + struct isis_area *area = srn->area; + + /* Log any Prefix-SID change in the LSPDB. */ + if (IS_DEBUG_ISIS(DEBUG_SR)) { + if (CHECK_FLAG(srp->parse_flags, F_ISIS_SR_PREFIX_SID_NEW)) + zlog_debug( + "ISIS-SR (%s) Prefix-SID created: %pFX (sysid %s)", + area->area_tag, &srp->prefix, + sysid_print(srn->sysid)); + else if (CHECK_FLAG(srp->parse_flags, + F_ISIS_SR_PREFIX_SID_MODIFIED)) + zlog_debug( + "ISIS-SR (%s) Prefix-SID modified: %pFX (sysid %s)", + area->area_tag, &srp->prefix, + sysid_print(srn->sysid)); + else if (!CHECK_FLAG(srp->parse_flags, + F_ISIS_SR_PREFIX_SID_UNCHANGED)) + zlog_debug( + "ISIS-SR (%s) Prefix-SID removed: %pFX (sysid %s)", + area->area_tag, &srp->prefix, + sysid_print(srn->sysid)); + } + + /* Install/reinstall/uninstall Prefix-SID if necessary. */ + if (CHECK_FLAG(srp->parse_flags, F_ISIS_SR_PREFIX_SID_NEW)) + isis_sr_prefix_install(srp); + else if (CHECK_FLAG(srp->parse_flags, F_ISIS_SR_PREFIX_SID_MODIFIED)) + isis_sr_prefix_reinstall(srp, false); + else if (!CHECK_FLAG(srp->parse_flags, + F_ISIS_SR_PREFIX_SID_UNCHANGED)) { + isis_sr_prefix_del(area, srn, srp); + return; + } + + srp->parse_flags = 0; +} + +/* Process any new/deleted/modified SRGB in the LSPDB. */ +static void isis_sr_process_node_changes(struct isis_area *area, int level, + struct sr_node *srn) +{ + struct sr_prefix *srp; + uint8_t sysid[ISIS_SYS_ID_LEN]; + bool adjacent; + + memcpy(sysid, srn->sysid, sizeof(sysid)); + + /* Log any SRGB change in the LSPDB. */ + if (IS_DEBUG_ISIS(DEBUG_SR)) { + if (CHECK_FLAG(srn->parse_flags, F_ISIS_SR_NODE_NEW)) + zlog_debug("ISIS-SR (%s) SRGB created (sysid %s)", + area->area_tag, sysid_print(sysid)); + else if (CHECK_FLAG(srn->parse_flags, F_ISIS_SR_NODE_MODIFIED)) + zlog_debug("ISIS-SR (%s) SRGB modified (sysid %s)", + area->area_tag, sysid_print(sysid)); + else if (!CHECK_FLAG(srn->parse_flags, + F_ISIS_SR_NODE_UNCHANGED)) + zlog_debug("ISIS-SR (%s) SRGB removed (sysid %s)", + area->area_tag, sysid_print(sysid)); + } + + /* + * If an adjacent router's SRGB was changed or created, then reinstall + * all Prefix-SIDs from all nodes. + */ + adjacent = isis_adj_exists(area, level, sysid); + if (CHECK_FLAG(srn->parse_flags, + F_ISIS_SR_NODE_NEW | F_ISIS_SR_NODE_MODIFIED)) { + if (adjacent) + isis_sr_adj_srgb_update(area, sysid, level); + } else if (!CHECK_FLAG(srn->parse_flags, F_ISIS_SR_NODE_UNCHANGED)) { + isis_sr_node_del(area, level, srn); + + if (adjacent) + isis_sr_adj_srgb_update(area, sysid, level); + return; + } + + srn->parse_flags = 0; + + frr_each_safe (tree_sr_node_prefix, &srn->prefix_sids, srp) + isis_sr_process_prefix_changes(srn, srp); +} + +/* Parse and process all SR-related Sub-TLVs after running the SPF algorithm. */ +void isis_area_verify_sr(struct isis_area *area) +{ + struct sr_node *srn; + + if (!area->srdb.enabled) + return; + + /* Parse LSPDB to detect new/deleted/modified SR (sub-)TLVs. */ + isis_sr_parse_lspdb(area); + + /* Process possible SR-related changes in the LDPSB. */ + for (int level = ISIS_LEVEL1; level <= ISIS_LEVELS; level++) { + frr_each_safe (tree_sr_node, &area->srdb.sr_nodes[level - 1], + srn) + isis_sr_process_node_changes(area, level, srn); + } +} + +/* + * Once a route is updated in the SPT, reinstall or uninstall its corresponding + * Prefix-SID (if any). + */ +static int isis_sr_route_update(struct isis_area *area, struct prefix *prefix, + struct isis_route_info *route_info) +{ + struct sr_prefix *srp; + + if (!area->srdb.enabled) + return 0; + + switch (area->is_type) { + case IS_LEVEL_1: + srp = isis_sr_prefix_find_area(area, ISIS_LEVEL1, prefix); + break; + case IS_LEVEL_2: + srp = isis_sr_prefix_find_area(area, ISIS_LEVEL2, prefix); + break; + case IS_LEVEL_1_AND_2: + srp = isis_sr_prefix_find_area(area, ISIS_LEVEL1, prefix); + if (!srp) + srp = isis_sr_prefix_find_area(area, ISIS_LEVEL2, + prefix); + break; + default: + flog_err(EC_LIB_DEVELOPMENT, "%s: unknown area level", + __func__); + exit(1); + } + + if (!srp || srp->type == ISIS_SR_PREFIX_LOCAL) + return 0; + + if (CHECK_FLAG(route_info->flag, ISIS_ROUTE_FLAG_ACTIVE)) { + isis_sr_prefix_reinstall(srp, true); + srp->u.remote.rinfo = route_info; + } else { + isis_sr_prefix_uninstall(srp); + srp->u.remote.rinfo = NULL; + } + + return 0; +} + +/*----------------------------------------------------------------------------*/ + +/* Install or uninstall (LAN)-Adj-SID. */ +static void isis_sr_adj_sid_install_uninstall(bool install, + const struct sr_adjacency *sra) +{ + struct zapi_labels zl; + struct zapi_nexthop_label *znh; + int cmd; + + cmd = install ? ZEBRA_MPLS_LABELS_ADD : ZEBRA_MPLS_LABELS_DELETE; + + memset(&zl, 0, sizeof(zl)); + zl.type = ZEBRA_LSP_ISIS_SR; + zl.local_label = sra->nexthop.label; + zl.nexthop_num = 1; + znh = &zl.nexthops[0]; + znh->family = sra->nexthop.family; + znh->address = sra->nexthop.address; + znh->type = (sra->nexthop.family == AF_INET) + ? NEXTHOP_TYPE_IPV4_IFINDEX + : NEXTHOP_TYPE_IPV6_IFINDEX; + znh->ifindex = sra->adj->circuit->interface->ifindex; + znh->label = MPLS_LABEL_IMPLICIT_NULL; + + (void)zebra_send_mpls_labels(zclient, cmd, &zl); +} + +/* Add new local Adj-SID. */ +static void isis_sr_adj_sid_add_single(struct isis_adjacency *adj, int family, + bool backup) +{ + struct isis_circuit *circuit = adj->circuit; + struct isis_area *area = circuit->area; + struct sr_adjacency *sra; + struct isis_adj_sid *adj_sid; + struct isis_lan_adj_sid *ladj_sid; + union g_addr nexthop = {}; + uint8_t flags; + mpls_label_t local_label; + + switch (family) { + case AF_INET: + if (!circuit->ip_router) + return; + + nexthop.ipv4 = adj->ipv4_addresses[0]; + break; + case AF_INET6: + if (!circuit->ipv6_router) + return; + + nexthop.ipv6 = adj->ipv6_addresses[0]; + break; + default: + flog_err(EC_LIB_DEVELOPMENT, + "%s: unexpected address-family: %u", __func__, family); + exit(1); + } + + flags = EXT_SUBTLV_LINK_ADJ_SID_VFLG | EXT_SUBTLV_LINK_ADJ_SID_LFLG; + if (family == AF_INET6) + SET_FLAG(flags, EXT_SUBTLV_LINK_ADJ_SID_FFLG); + if (backup) + SET_FLAG(flags, EXT_SUBTLV_LINK_ADJ_SID_BFLG); + + local_label = isis_zebra_request_dynamic_label(); + if (circuit->ext == NULL) + circuit->ext = isis_alloc_ext_subtlvs(); + + if (IS_DEBUG_ISIS(DEBUG_SR)) { + char buf[INET6_ADDRSTRLEN]; + + inet_ntop(family, &nexthop, buf, sizeof(buf)); + zlog_debug("ISIS-SR (%s) installing Adj-SID %s%%%s label %u", + area->area_tag, buf, circuit->interface->name, + local_label); + } + + sra = XCALLOC(MTYPE_ISIS_SR_INFO, sizeof(*sra)); + sra->type = backup ? ISIS_SR_LAN_BACKUP : ISIS_SR_ADJ_NORMAL; + sra->nexthop.family = family; + sra->nexthop.address = nexthop; + sra->nexthop.label = local_label; + switch (circuit->circ_type) { + case CIRCUIT_T_BROADCAST: + ladj_sid = XCALLOC(MTYPE_ISIS_SUBTLV, sizeof(*ladj_sid)); + ladj_sid->family = family; + ladj_sid->flags = flags; + ladj_sid->weight = 0; + memcpy(ladj_sid->neighbor_id, adj->sysid, + sizeof(ladj_sid->neighbor_id)); + ladj_sid->sid = local_label; + isis_tlvs_add_lan_adj_sid(circuit->ext, ladj_sid); + sra->u.ladj_sid = ladj_sid; + break; + case CIRCUIT_T_P2P: + adj_sid = XCALLOC(MTYPE_ISIS_SUBTLV, sizeof(*adj_sid)); + adj_sid->family = family; + adj_sid->flags = flags; + adj_sid->weight = 0; + adj_sid->sid = local_label; + isis_tlvs_add_adj_sid(circuit->ext, adj_sid); + sra->u.adj_sid = adj_sid; + break; + default: + flog_err(EC_LIB_DEVELOPMENT, "%s: unexpected circuit type: %u", + __func__, circuit->circ_type); + exit(1); + } + sra->adj = adj; + listnode_add(area->srdb.adj_sids, sra); + listnode_add(adj->adj_sids, sra); + + isis_sr_adj_sid_install_uninstall(true, sra); +} + +static void isis_sr_adj_sid_add(struct isis_adjacency *adj, int family) +{ + isis_sr_adj_sid_add_single(adj, family, false); + isis_sr_adj_sid_add_single(adj, family, true); +} + +/* Delete local Adj-SID. */ +static void isis_sr_adj_sid_del(struct sr_adjacency *sra) +{ + struct isis_circuit *circuit = sra->adj->circuit; + struct isis_area *area = circuit->area; + + if (IS_DEBUG_ISIS(DEBUG_SR)) { + char buf[INET6_ADDRSTRLEN]; + + inet_ntop(sra->nexthop.family, &sra->nexthop.address, buf, + sizeof(buf)); + zlog_debug("ISIS-SR (%s) uninstalling Adj-SID %s%%%s", + area->area_tag, buf, circuit->interface->name); + } + + isis_sr_adj_sid_install_uninstall(false, sra); + + switch (circuit->circ_type) { + case CIRCUIT_T_BROADCAST: + isis_zebra_release_dynamic_label(sra->u.ladj_sid->sid); + isis_tlvs_del_lan_adj_sid(circuit->ext, sra->u.ladj_sid); + break; + case CIRCUIT_T_P2P: + isis_zebra_release_dynamic_label(sra->u.adj_sid->sid); + isis_tlvs_del_adj_sid(circuit->ext, sra->u.adj_sid); + break; + default: + flog_err(EC_LIB_DEVELOPMENT, "%s: unexpected circuit type: %u", + __func__, circuit->circ_type); + exit(1); + } + + listnode_delete(area->srdb.adj_sids, sra); + listnode_delete(sra->adj->adj_sids, sra); + XFREE(MTYPE_ISIS_SR_INFO, sra); +} + +/* Remove all Adj-SIDs associated to an adjacency that is going down. */ +static int isis_sr_adj_state_change(struct isis_adjacency *adj) +{ + struct sr_adjacency *sra; + struct listnode *node, *nnode; + + if (!adj->circuit->area->srdb.enabled) + return 0; + + if (adj->adj_state == ISIS_ADJ_UP) + return 0; + + for (ALL_LIST_ELEMENTS(adj->adj_sids, node, nnode, sra)) + isis_sr_adj_sid_del(sra); + + return 0; +} + +/* + * Adjacency now has one or more IPv4/IPv6 addresses. Add new IPv4 or IPv6 + * Adj-SID accordingly. + */ +static int isis_sr_adj_ip_enabled(struct isis_adjacency *adj, int family) +{ + if (!adj->circuit->area->srdb.enabled) + return 0; + + isis_sr_adj_sid_add(adj, family); + + return 0; +} + +/* + * Adjacency doesn't have any IPv4 or IPv6 addresses anymore. Delete the + * corresponding Adj-SID(s) accordingly. + */ +static int isis_sr_adj_ip_disabled(struct isis_adjacency *adj, int family) +{ + struct sr_adjacency *sra; + struct listnode *node, *nnode; + + if (!adj->circuit->area->srdb.enabled) + return 0; + + for (ALL_LIST_ELEMENTS(adj->adj_sids, node, nnode, sra)) + if (sra->nexthop.family == family) + isis_sr_adj_sid_del(sra); + + return 0; +} + +static int isis_sr_if_new_hook(struct interface *ifp) +{ + struct isis_circuit *circuit; + struct isis_area *area; + struct connected *connected; + struct listnode *node; + + circuit = circuit_scan_by_ifp(ifp); + if (!circuit) + return 0; + + area = circuit->area; + if (!area) + return 0; + + /* + * Update the Node-SID flag of the configured Prefix-SID mappings if + * necessary. This needs to be done here since isisd reads the startup + * configuration before receiving interface information from zebra. + */ + FOR_ALL_INTERFACES_ADDRESSES (ifp, connected, node) { + struct sr_prefix_cfg *pcfg; + + pcfg = isis_sr_cfg_prefix_find(area, connected->address); + if (!pcfg) + continue; + + if (isis_sr_prefix_is_node_sid(ifp, &pcfg->prefix) + && !pcfg->node_sid) { + pcfg->node_sid = true; + lsp_regenerate_schedule(area, area->is_type, 0); + } + } + + return 0; +} + +/*----------------------------------------------------------------------------*/ + +static void isis_sr_show_prefix_sid_local(struct vty *vty, struct ttable *tt, + const struct isis_area *area, + const struct sr_prefix *srp) +{ + const struct sr_nexthop_info *srnh = &srp->u.local.info; + char buf_prefix[BUFSIZ]; + char buf_llabel[BUFSIZ]; + char buf_rlabel[BUFSIZ]; + char buf_uptime[BUFSIZ]; + + if (srp->local_label != MPLS_INVALID_LABEL) + label2str(srp->local_label, buf_llabel, sizeof(buf_llabel)); + else + snprintf(buf_llabel, sizeof(buf_llabel), "-"); + if (srnh->label != MPLS_INVALID_LABEL) { + label2str(srnh->label, buf_rlabel, sizeof(buf_rlabel)); + log_uptime(srnh->uptime, buf_uptime, sizeof(buf_uptime)); + } else { + snprintf(buf_rlabel, sizeof(buf_rlabel), "-"); + snprintf(buf_uptime, sizeof(buf_uptime), "-"); + } + + ttable_add_row(tt, "%s|%u|%s|local|%s|%s", + prefix2str(&srp->prefix, buf_prefix, sizeof(buf_prefix)), + srp->sid.value, buf_llabel, buf_rlabel, buf_uptime); +} + +static void isis_sr_show_prefix_sid_remote(struct vty *vty, struct ttable *tt, + const struct isis_area *area, + const struct sr_prefix *srp) +{ + struct isis_nexthop *nexthop; + struct listnode *node; + char buf_prefix[BUFSIZ]; + char buf_llabel[BUFSIZ]; + char buf_nhop[BUFSIZ]; + char buf_iface[BUFSIZ]; + char buf_rlabel[BUFSIZ]; + char buf_uptime[BUFSIZ]; + bool first = true; + + (void)prefix2str(&srp->prefix, buf_prefix, sizeof(buf_prefix)); + if (srp->local_label != MPLS_INVALID_LABEL) + label2str(srp->local_label, buf_llabel, sizeof(buf_llabel)); + else + snprintf(buf_llabel, sizeof(buf_llabel), "-"); + + if (!srp->u.remote.rinfo) { + ttable_add_row(tt, "%s|%u|%s|-|-|-", buf_prefix, srp->sid.value, + buf_llabel); + return; + } + + for (ALL_LIST_ELEMENTS_RO(srp->u.remote.rinfo->nexthops, node, + nexthop)) { + struct interface *ifp; + + inet_ntop(nexthop->family, &nexthop->ip, buf_nhop, + sizeof(buf_nhop)); + ifp = if_lookup_by_index(nexthop->ifindex, VRF_DEFAULT); + if (ifp) + strlcpy(buf_iface, ifp->name, sizeof(buf_iface)); + else + snprintf(buf_iface, sizeof(buf_iface), "ifindex %u", + nexthop->ifindex); + if (nexthop->sr.label == MPLS_INVALID_LABEL) { + snprintf(buf_rlabel, sizeof(buf_rlabel), "-"); + snprintf(buf_uptime, sizeof(buf_uptime), "-"); + } else { + label2str(nexthop->sr.label, buf_rlabel, + sizeof(buf_rlabel)); + log_uptime(nexthop->sr.uptime, buf_uptime, + sizeof(buf_uptime)); + } + + if (first) + ttable_add_row(tt, "%s|%u|%s|%s, %s|%s|%s", buf_prefix, + srp->sid.value, buf_llabel, buf_nhop, + buf_iface, buf_rlabel, buf_uptime); + else + ttable_add_row(tt, "|||%s, %s|%s|%s", buf_nhop, + buf_iface, buf_rlabel, buf_uptime); + first = false; + } +} + +static void isis_sr_show_prefix_sids(struct vty *vty, struct isis_area *area, + int level) +{ + struct sr_prefix *srp; + struct ttable *tt; + + if (tree_sr_area_prefix_count(&area->srdb.prefix_sids[level - 1]) == 0) + return; + + vty_out(vty, " IS-IS %s Prefix-SIDs:\n\n", circuit_t2string(level)); + + /* Prepare table. */ + tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]); + ttable_add_row(tt, "Prefix|SID|In Label|Nexthop|Out Label|Uptime"); + tt->style.cell.rpad = 2; + tt->style.corner = '+'; + ttable_restyle(tt); + ttable_rowseps(tt, 0, BOTTOM, true, '-'); + + frr_each (tree_sr_area_prefix, &area->srdb.prefix_sids[level - 1], + srp) { + switch (srp->type) { + case ISIS_SR_PREFIX_LOCAL: + isis_sr_show_prefix_sid_local(vty, tt, area, srp); + break; + case ISIS_SR_PREFIX_REMOTE: + isis_sr_show_prefix_sid_remote(vty, tt, area, srp); + break; + } + } + + /* 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_sr_prefix_sids, show_sr_prefix_sids_cmd, + "show isis segment-routing prefix-sids", + SHOW_STR PROTO_HELP + "Segment-Routing\n" + "Segment-Routing Prefix-SIDs\n") +{ + struct listnode *node; + struct isis_area *area; + + for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) { + vty_out(vty, "Area %s:\n", + area->area_tag ? area->area_tag : "null"); + + for (int level = ISIS_LEVEL1; level <= ISIS_LEVELS; level++) + isis_sr_show_prefix_sids(vty, area, level); + } + + return CMD_SUCCESS; +} + +/*----------------------------------------------------------------------------*/ + +/* Try to enable SR on the given IS-IS area. */ +int isis_sr_start(struct isis_area *area) +{ + struct isis_sr_db *srdb = &area->srdb; + struct isis_circuit *circuit; + struct listnode *node; + + /* + * Request SGRB to the label manager. If the allocation fails, return + * an error to disable SR until a new SRGB is successfully allocated. + */ + if (isis_zebra_request_label_range( + srdb->config.srgb_lower_bound, + srdb->config.srgb_upper_bound + - srdb->config.srgb_lower_bound + 1)) + return -1; + + if (IS_DEBUG_ISIS(DEBUG_SR)) + zlog_debug("ISIS-SR (%s) Starting Segment Routing", + area->area_tag); + + /* Create Adj-SIDs for existing adjacencies. */ + for (ALL_LIST_ELEMENTS_RO(area->circuit_list, node, circuit)) { + struct isis_adjacency *adj; + struct listnode *anode; + + switch (circuit->circ_type) { + case CIRCUIT_T_BROADCAST: + for (int level = ISIS_LEVEL1; level <= ISIS_LEVELS; + level++) { + for (ALL_LIST_ELEMENTS_RO( + circuit->u.bc.adjdb[level - 1], + anode, adj)) { + if (adj->ipv4_address_count > 0) + isis_sr_adj_sid_add(adj, + AF_INET); + if (adj->ipv6_address_count > 0) + isis_sr_adj_sid_add(adj, + AF_INET6); + } + } + break; + case CIRCUIT_T_P2P: + adj = circuit->u.p2p.neighbor; + if (adj && adj->ipv4_address_count > 0) + isis_sr_adj_sid_add(adj, AF_INET); + if (adj && adj->ipv6_address_count > 0) + isis_sr_adj_sid_add(adj, AF_INET6); + break; + default: + break; + } + } + + /* Regenerate LSPs. */ + lsp_regenerate_schedule(area, area->is_type, 0); + + return 0; +} + +/* Disable SR on the given IS-IS area. */ +void isis_sr_stop(struct isis_area *area) +{ + struct isis_sr_db *srdb = &area->srdb; + struct sr_adjacency *sra; + struct listnode *node, *nnode; + + if (IS_DEBUG_ISIS(DEBUG_SR)) + zlog_debug("ISIS-SR (%s) Stopping Segment Routing", + area->area_tag); + + /* Uninstall Adj-SIDs. */ + for (ALL_LIST_ELEMENTS(area->srdb.adj_sids, node, nnode, sra)) + isis_sr_adj_sid_del(sra); + + /* Uninstall Prefix-SIDs. */ + for (int level = ISIS_LEVEL1; level <= ISIS_LEVELS; level++) { + while (tree_sr_node_count(&srdb->sr_nodes[level - 1]) > 0) { + struct sr_node *srn; + + srn = tree_sr_node_first(&srdb->sr_nodes[level - 1]); + isis_sr_node_del(area, level, srn); + } + } + + /* Release SRGB. */ + isis_zebra_release_label_range(srdb->config.srgb_lower_bound, + srdb->config.srgb_upper_bound); + + /* Regenerate LSPs. */ + lsp_regenerate_schedule(area, area->is_type, 0); +} + +void isis_sr_area_init(struct isis_area *area) +{ + struct isis_sr_db *srdb = &area->srdb; + + memset(srdb, 0, sizeof(*srdb)); + srdb->enabled = false; + srdb->adj_sids = list_new(); + + for (int level = ISIS_LEVEL1; level <= ISIS_LEVELS; level++) { + tree_sr_node_init(&srdb->sr_nodes[level - 1]); + tree_sr_area_prefix_init(&srdb->prefix_sids[level - 1]); + } + + /* Pull defaults from the YANG module. */ +#ifndef FABRICD + srdb->config.enabled = yang_get_default_bool("%s/enabled", ISIS_SR); + srdb->config.srgb_lower_bound = + yang_get_default_uint32("%s/srgb/lower-bound", ISIS_SR); + srdb->config.srgb_upper_bound = + yang_get_default_uint32("%s/srgb/upper-bound", ISIS_SR); +#else + srdb->config.enabled = false; + srdb->config.srgb_lower_bound = SRGB_LOWER_BOUND; + srdb->config.srgb_upper_bound = SRGB_UPPER_BOUND; +#endif + srdb->config.msd = 0; + tree_sr_prefix_cfg_init(&srdb->config.prefix_sids); +} + +void isis_sr_area_term(struct isis_area *area) +{ + struct isis_sr_db *srdb = &area->srdb; + + /* Stop Segment Routing */ + if (area->srdb.enabled) + isis_sr_stop(area); + + /* Clear Prefix-SID configuration. */ + while (tree_sr_prefix_cfg_count(&srdb->config.prefix_sids) > 0) { + struct sr_prefix_cfg *pcfg; + + pcfg = tree_sr_prefix_cfg_first(&srdb->config.prefix_sids); + isis_sr_cfg_prefix_del(pcfg); + } +} + +void isis_sr_init(void) +{ + install_element(VIEW_NODE, &show_sr_prefix_sids_cmd); + + /* Register hooks. */ + hook_register(isis_adj_state_change_hook, isis_sr_adj_state_change); + hook_register(isis_adj_ip_enabled_hook, isis_sr_adj_ip_enabled); + hook_register(isis_adj_ip_disabled_hook, isis_sr_adj_ip_disabled); + hook_register(isis_route_update_hook, isis_sr_route_update); + hook_register(isis_if_new_hook, isis_sr_if_new_hook); +} + +void isis_sr_term(void) +{ + /* Unregister hooks. */ + hook_unregister(isis_adj_state_change_hook, isis_sr_adj_state_change); + hook_unregister(isis_adj_ip_enabled_hook, isis_sr_adj_ip_enabled); + hook_unregister(isis_adj_ip_disabled_hook, isis_sr_adj_ip_disabled); + hook_unregister(isis_route_update_hook, isis_sr_route_update); + hook_unregister(isis_if_new_hook, isis_sr_if_new_hook); +} diff --git a/isisd/isis_sr.h b/isisd/isis_sr.h new file mode 100644 index 000000000..286ebeb95 --- /dev/null +++ b/isisd/isis_sr.h @@ -0,0 +1,259 @@ +/* + * This is an implementation of Segment Routing for IS-IS + * as per draft draft-ietf-isis-segment-routing-extensions-25 + * + * Copyright (C) 2019 Orange Labs http://www.orange.com + * + * Author: Olivier Dugeon <olivier.dugeon@orange.com> + * Contributor: Renato Westphal <renato@opensourcerouting.org> for NetDEF + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; see the file COPYING; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _FRR_ISIS_SR_H +#define _FRR_ISIS_SR_H + +#include "lib/linklist.h" +#include "lib/mpls.h" +#include "lib/nexthop.h" +#include "lib/typesafe.h" + +#include "isisd/isis_tlvs.h" + +/* + * Segment Routing information is transported through the following Sub-TLVs: + * + * Sub-TLV Name Value TLVs + * --------------------------------------------------------------------- + * SID Label 1 + * + * Prefix Segment Identifier 3 135, 235, 236 and 237 + * + * Adjacency Segment Identifier 31 22, 23, 141, 222 and 223 + * LAN Adjacency Segment Identifier 32 22, 23, 141, 222 and 223 + * + * Segment Routing Capability 2 242 + * Segment Routing Algorithm 19 242 + * Node Maximum Stack Depth (MSD) 23 242 + * + * Sub-TLV definitions, serialization and de-serialization are defined + * in isis_tlvs.[c,h]. + */ + +#define SRGB_LOWER_BOUND 16000 +#define SRGB_UPPER_BOUND 23999 + +PREDECL_RBTREE_UNIQ(tree_sr_node) +PREDECL_RBTREE_UNIQ(tree_sr_node_prefix) +PREDECL_RBTREE_UNIQ(tree_sr_area_prefix) +PREDECL_RBTREE_UNIQ(tree_sr_prefix_cfg) + +/* SR Adj-SID type. */ +enum sr_adj_type { + ISIS_SR_ADJ_NORMAL = 0, + ISIS_SR_LAN_BACKUP, +}; + +/* SR Adjacency. */ +struct sr_adjacency { + /* Adjacency type. */ + enum sr_adj_type type; + + /* Adj-SID nexthop information. */ + struct { + int family; + union g_addr address; + mpls_label_t label; + } nexthop; + + /* (LAN-)Adj-SID Sub-TLV. */ + union { + struct isis_adj_sid *adj_sid; + struct isis_lan_adj_sid *ladj_sid; + } u; + + /* Back pointer to IS-IS adjacency. */ + struct isis_adjacency *adj; +}; + +/* SR Prefix-SID type. */ +enum sr_prefix_type { + ISIS_SR_PREFIX_LOCAL = 0, + ISIS_SR_PREFIX_REMOTE, +}; + +/* SR Nexthop Information. */ +struct sr_nexthop_info { + mpls_label_t label; + time_t uptime; +}; + +/* SR Prefix-SID. */ +struct sr_prefix { + /* RB-tree entries. */ + struct tree_sr_node_prefix_item node_entry; + struct tree_sr_area_prefix_item area_entry; + + /* IP prefix. */ + struct prefix prefix; + + /* SID value, algorithm and flags. */ + struct isis_prefix_sid sid; + + /* Local label value. */ + mpls_label_t local_label; + + /* Prefix-SID type. */ + enum sr_prefix_type type; + union { + struct { + /* Information about this local Prefix-SID. */ + struct sr_nexthop_info info; + } local; + struct { + /* Route associated to this remote Prefix-SID. */ + struct isis_route_info *rinfo; + } remote; + } u; + + /* Backpointer to SR node. */ + struct sr_node *srn; + + /* Flags used while the LSPDB is being parsed. */ + uint8_t parse_flags; +#define F_ISIS_SR_PREFIX_SID_NEW 0x01 +#define F_ISIS_SR_PREFIX_SID_MODIFIED 0x02 +#define F_ISIS_SR_PREFIX_SID_UNCHANGED 0x04 +}; + +/* SR node. */ +struct sr_node { + /* RB-tree entry. */ + struct tree_sr_node_item entry; + + /* IS-IS level: ISIS_LEVEL1 or ISIS_LEVEL2. */ + int level; + + /* IS-IS node identifier. */ + uint8_t sysid[ISIS_SYS_ID_LEN]; + + /* IS-IS node capabilities (SRGB, SR Algorithms, etc). */ + struct isis_router_cap cap; + + /* List of Prefix-SIDs advertised by this node. */ + struct tree_sr_node_prefix_head prefix_sids; + + /* Backpointer to IS-IS area. */ + struct isis_area *area; + + /* Flags used while the LSPDB is being parsed. */ + uint8_t parse_flags; +#define F_ISIS_SR_NODE_NEW 0x01 +#define F_ISIS_SR_NODE_MODIFIED 0x02 +#define F_ISIS_SR_NODE_UNCHANGED 0x04 +}; + +/* NOTE: these values must be in sync with the YANG module. */ +enum sr_sid_value_type { + SR_SID_VALUE_TYPE_INDEX = 0, + SR_SID_VALUE_TYPE_ABSOLUTE = 1, +}; + +/* NOTE: these values must be in sync with the YANG module. */ +enum sr_last_hop_behavior { + SR_LAST_HOP_BEHAVIOR_EXP_NULL = 0, + SR_LAST_HOP_BEHAVIOR_NO_PHP = 1, + SR_LAST_HOP_BEHAVIOR_PHP = 2, +}; + +/* SR Prefix-SID configuration. */ +struct sr_prefix_cfg { + /* RB-tree entry. */ + struct tree_sr_prefix_cfg_item entry; + + /* IP prefix. */ + struct prefix prefix; + + /* SID value. */ + uint32_t sid; + + /* SID value type. */ + enum sr_sid_value_type sid_type; + + /* SID last hop behavior. */ + enum sr_last_hop_behavior last_hop_behavior; + + /* Does this Prefix-SID refer to a loopback address (Node-SID)? */ + bool node_sid; + + /* Backpointer to IS-IS area. */ + struct isis_area *area; +}; + +/* Per-area IS-IS Segment Routing information. */ +struct isis_sr_db { + /* Operational status of Segment Routing. */ + bool enabled; + + /* Adj-SIDs. */ + struct list *adj_sids; + + /* SR information from all nodes. */ + struct tree_sr_node_head sr_nodes[ISIS_LEVELS]; + + /* Prefix-SIDs. */ + struct tree_sr_area_prefix_head prefix_sids[ISIS_LEVELS]; + + /* Area SR configuration. */ + struct { + /* Administrative status of Segment Routing. */ + bool enabled; + + /* Segment Routing Global Block lower & upper bound. */ + uint32_t srgb_lower_bound; + uint32_t srgb_upper_bound; + + /* Maximum SID Depth supported by the node. */ + uint8_t msd; + + /* Prefix-SID mappings. */ + struct tree_sr_prefix_cfg_head prefix_sids; + } config; +}; + +/* Prototypes. */ +extern int isis_sr_cfg_srgb_update(struct isis_area *area, uint32_t lower_bound, + uint32_t upper_bound); +extern void isis_sr_cfg_msd_update(struct isis_area *area); +extern struct sr_prefix_cfg * +isis_sr_cfg_prefix_add(struct isis_area *area, const struct prefix *prefix); +extern void isis_sr_cfg_prefix_del(struct sr_prefix_cfg *pcfg); +extern struct sr_prefix_cfg * +isis_sr_cfg_prefix_find(struct isis_area *area, union prefixconstptr prefix); +extern void isis_sr_prefix_cfg2subtlv(const struct sr_prefix_cfg *pcfg, + bool external, + struct isis_prefix_sid *psid); +extern void isis_sr_nexthop_update(struct sr_nexthop_info *srnh, + mpls_label_t label); +extern void isis_sr_nexthop_reset(struct sr_nexthop_info *srnh); +extern void isis_area_verify_sr(struct isis_area *area); +extern int isis_sr_start(struct isis_area *area); +extern void isis_sr_stop(struct isis_area *area); +extern void isis_sr_area_init(struct isis_area *area); +extern void isis_sr_area_term(struct isis_area *area); +extern void isis_sr_init(void); +extern void isis_sr_term(void); + +#endif /* _FRR_ISIS_SR_H */ diff --git a/isisd/isis_tlvs.c b/isisd/isis_tlvs.c index 761005d0c..be88ee85a 100644 --- a/isisd/isis_tlvs.c +++ b/isisd/isis_tlvs.c @@ -43,9 +43,10 @@ #include "isisd/isis_pdu.h" #include "isisd/isis_lsp.h" #include "isisd/isis_te.h" +#include "isisd/isis_sr.h" DEFINE_MTYPE_STATIC(ISISD, ISIS_TLV, "ISIS TLVs") -DEFINE_MTYPE_STATIC(ISISD, ISIS_SUBTLV, "ISIS Sub-TLVs") +DEFINE_MTYPE(ISISD, ISIS_SUBTLV, "ISIS Sub-TLVs") DEFINE_MTYPE_STATIC(ISISD, ISIS_MT_ITEM_LIST, "ISIS MT Item Lists") typedef int (*unpack_tlv_func)(enum isis_tlv_context context, uint8_t tlv_type, @@ -887,7 +888,11 @@ static int unpack_item_prefix_sid(uint16_t mtid, uint8_t len, struct stream *s, if (sid.flags & ISIS_PREFIX_SID_VALUE) { sid.value = stream_get3(s); - sid.value &= MPLS_LABEL_VALUE_MASK; + if (!IS_MPLS_UNRESERVED_LABEL(sid.value)) { + sbuf_push(log, indent, "Invalid absolute SID %u\n", + sid.value); + return 1; + } } else { sid.value = stream_getl(s); } @@ -2623,7 +2628,7 @@ static void format_tlv_router_cap(const struct isis_router_cap *router_cap, sbuf_push(buf, indent, " Algorithm: %s", router_cap->algo[0] == 0 ? "0: SPF" : "0: Strict SPF"); - for (int i = 0; i < SR_ALGORITHM_COUNT; i++) + for (int i = 1; i < SR_ALGORITHM_COUNT; i++) if (router_cap->algo[i] != SR_ALGORITHM_UNSET) sbuf_push(buf, indent, " %s", router_cap->algo[1] == 0 @@ -4630,24 +4635,42 @@ void isis_tlvs_del_lan_adj_sid(struct isis_ext_subtlvs *exts, } void isis_tlvs_add_extended_ip_reach(struct isis_tlvs *tlvs, - struct prefix_ipv4 *dest, uint32_t metric) + struct prefix_ipv4 *dest, uint32_t metric, + bool external, struct sr_prefix_cfg *pcfg) { struct isis_extended_ip_reach *r = XCALLOC(MTYPE_ISIS_TLV, sizeof(*r)); r->metric = metric; memcpy(&r->prefix, dest, sizeof(*dest)); apply_mask_ipv4(&r->prefix); + if (pcfg) { + struct isis_prefix_sid *psid = + XCALLOC(MTYPE_ISIS_SUBTLV, sizeof(*psid)); + + isis_sr_prefix_cfg2subtlv(pcfg, external, psid); + r->subtlvs = isis_alloc_subtlvs(ISIS_CONTEXT_SUBTLV_IP_REACH); + append_item(&r->subtlvs->prefix_sids, (struct isis_item *)psid); + } append_item(&tlvs->extended_ip_reach, (struct isis_item *)r); } void isis_tlvs_add_ipv6_reach(struct isis_tlvs *tlvs, uint16_t mtid, - struct prefix_ipv6 *dest, uint32_t metric) + struct prefix_ipv6 *dest, uint32_t metric, + bool external, struct sr_prefix_cfg *pcfg) { struct isis_ipv6_reach *r = XCALLOC(MTYPE_ISIS_TLV, sizeof(*r)); r->metric = metric; memcpy(&r->prefix, dest, sizeof(*dest)); apply_mask_ipv6(&r->prefix); + if (pcfg) { + struct isis_prefix_sid *psid = + XCALLOC(MTYPE_ISIS_SUBTLV, sizeof(*psid)); + + isis_sr_prefix_cfg2subtlv(pcfg, external, psid); + r->subtlvs = isis_alloc_subtlvs(ISIS_CONTEXT_SUBTLV_IP_REACH); + append_item(&r->subtlvs->prefix_sids, (struct isis_item *)psid); + } struct isis_item_list *l; l = (mtid == ISIS_MT_IPV4_UNICAST) @@ -4661,7 +4684,7 @@ void isis_tlvs_add_ipv6_dstsrc_reach(struct isis_tlvs *tlvs, uint16_t mtid, struct prefix_ipv6 *src, uint32_t metric) { - isis_tlvs_add_ipv6_reach(tlvs, mtid, dest, metric); + isis_tlvs_add_ipv6_reach(tlvs, mtid, dest, metric, false, NULL); struct isis_item_list *l = isis_get_mt_items(&tlvs->mt_ipv6_reach, mtid); diff --git a/isisd/isis_tlvs.h b/isisd/isis_tlvs.h index 2948728e2..c3b25669b 100644 --- a/isisd/isis_tlvs.h +++ b/isisd/isis_tlvs.h @@ -28,8 +28,11 @@ #include "openbsd-tree.h" #include "prefix.h" +DECLARE_MTYPE(ISIS_SUBTLV) + struct lspdb_head; struct isis_subtlvs; +struct sr_prefix_cfg; struct isis_area_address; struct isis_area_address { @@ -580,9 +583,11 @@ void isis_tlvs_set_te_router_id(struct isis_tlvs *tlvs, void isis_tlvs_add_oldstyle_ip_reach(struct isis_tlvs *tlvs, struct prefix_ipv4 *dest, uint8_t metric); void isis_tlvs_add_extended_ip_reach(struct isis_tlvs *tlvs, - struct prefix_ipv4 *dest, uint32_t metric); + struct prefix_ipv4 *dest, uint32_t metric, + bool external, struct sr_prefix_cfg *pcfg); void isis_tlvs_add_ipv6_reach(struct isis_tlvs *tlvs, uint16_t mtid, - struct prefix_ipv6 *dest, uint32_t metric); + struct prefix_ipv6 *dest, uint32_t metric, + bool external, struct sr_prefix_cfg *pcfg); void isis_tlvs_add_ipv6_dstsrc_reach(struct isis_tlvs *tlvs, uint16_t mtid, struct prefix_ipv6 *dest, struct prefix_ipv6 *src, diff --git a/isisd/isis_zebra.c b/isisd/isis_zebra.c index 630264768..3ce756231 100644 --- a/isisd/isis_zebra.c +++ b/isisd/isis_zebra.c @@ -50,8 +50,16 @@ #include "isisd/isis_route.h" #include "isisd/isis_zebra.h" #include "isisd/isis_te.h" +#include "isisd/isis_sr.h" -struct zclient *zclient = NULL; +struct zclient *zclient; +static struct zclient *zclient_sync; + +/* List of chunks of labels externally assigned by zebra. */ +static struct list *label_chunk_list; +static struct listnode *current_label_chunk; + +static void isis_zebra_label_manager_connect(void); /* Router-id update message from zebra. */ static int isis_router_id_update_zebra(ZAPI_CALLBACK_ARGS) @@ -245,6 +253,88 @@ void isis_zebra_route_del_route(struct prefix *prefix, zclient_route_send(ZEBRA_ROUTE_DELETE, zclient, &api); } +/* Install Prefix-SID in the forwarding plane. */ +void isis_zebra_install_prefix_sid(const struct sr_prefix *srp) +{ + struct zapi_labels zl; + struct zapi_nexthop_label *znh; + struct listnode *node; + struct isis_nexthop *nexthop; + struct interface *ifp; + + /* Prepare message. */ + memset(&zl, 0, sizeof(zl)); + zl.type = ZEBRA_LSP_ISIS_SR; + zl.local_label = srp->local_label; + + switch (srp->type) { + case ISIS_SR_PREFIX_LOCAL: + ifp = if_lookup_by_name("lo", VRF_DEFAULT); + if (!ifp) { + zlog_warn( + "%s: couldn't install Prefix-SID %pFX: loopback interface not found", + __func__, &srp->prefix); + return; + } + + znh = &zl.nexthops[zl.nexthop_num++]; + znh->type = NEXTHOP_TYPE_IFINDEX; + znh->ifindex = ifp->ifindex; + znh->label = MPLS_LABEL_IMPLICIT_NULL; + break; + case ISIS_SR_PREFIX_REMOTE: + /* Update route in the RIB too. */ + SET_FLAG(zl.message, ZAPI_LABELS_FTN); + zl.route.prefix = srp->prefix; + zl.route.type = ZEBRA_ROUTE_ISIS; + zl.route.instance = 0; + + for (ALL_LIST_ELEMENTS_RO(srp->u.remote.rinfo->nexthops, node, + nexthop)) { + if (nexthop->sr.label == MPLS_INVALID_LABEL) + continue; + + if (zl.nexthop_num >= MULTIPATH_NUM) + break; + + znh = &zl.nexthops[zl.nexthop_num++]; + znh->type = (srp->prefix.family == AF_INET) + ? NEXTHOP_TYPE_IPV4_IFINDEX + : NEXTHOP_TYPE_IPV6_IFINDEX; + znh->family = nexthop->family; + znh->address = nexthop->ip; + znh->ifindex = nexthop->ifindex; + znh->label = nexthop->sr.label; + } + break; + } + + /* Send message to zebra. */ + (void)zebra_send_mpls_labels(zclient, ZEBRA_MPLS_LABELS_REPLACE, &zl); +} + +/* Uninstall Prefix-SID from the forwarding plane. */ +void isis_zebra_uninstall_prefix_sid(const struct sr_prefix *srp) +{ + struct zapi_labels zl; + + /* Prepare message. */ + memset(&zl, 0, sizeof(zl)); + zl.type = ZEBRA_LSP_ISIS_SR; + zl.local_label = srp->local_label; + + if (srp->type == ISIS_SR_PREFIX_REMOTE) { + /* Update route in the RIB too. */ + SET_FLAG(zl.message, ZAPI_LABELS_FTN); + zl.route.prefix = srp->prefix; + zl.route.type = ZEBRA_ROUTE_ISIS; + zl.route.instance = 0; + } + + /* Send message to zebra. */ + (void)zebra_send_mpls_labels(zclient, ZEBRA_MPLS_LABELS_DELETE, &zl); +} + static int isis_zebra_read(ZAPI_CALLBACK_ARGS) { struct zapi_route api; @@ -302,13 +392,192 @@ void isis_zebra_redistribute_unset(afi_t afi, int type) type, 0, VRF_DEFAULT); } +/* Label Manager Requests. */ +int isis_zebra_request_label_range(uint32_t base, uint32_t chunk_size) +{ + int ret; + uint32_t start, end; + + if (zclient_sync->sock == -1) + isis_zebra_label_manager_connect(); + + ret = lm_get_label_chunk(zclient_sync, 0, base, chunk_size, &start, + &end); + if (ret < 0) { + zlog_warn("%s: error getting label range!", __func__); + return -1; + } + + return 0; +} + +void isis_zebra_release_label_range(uint32_t start, uint32_t end) +{ + int ret; + + if (zclient_sync->sock == -1) + isis_zebra_label_manager_connect(); + + ret = lm_release_label_chunk(zclient_sync, start, end); + if (ret < 0) + zlog_warn("%s: error releasing label range!", __func__); +} + +static int isis_zebra_get_label_chunk(void) +{ + int ret; + uint32_t start, end; + struct label_chunk *new_label_chunk; + + if (zclient_sync->sock == -1) + isis_zebra_label_manager_connect(); + + ret = lm_get_label_chunk(zclient_sync, 0, MPLS_LABEL_BASE_ANY, + CHUNK_SIZE, &start, &end); + if (ret < 0) { + zlog_warn("%s: error getting label chunk!", __func__); + return -1; + } + + new_label_chunk = calloc(1, sizeof(struct label_chunk)); + if (!new_label_chunk) { + zlog_warn("%s: error trying to allocate label chunk %u - %u", + __func__, start, end); + return -1; + } + + new_label_chunk->start = start; + new_label_chunk->end = end; + new_label_chunk->used_mask = 0; + + listnode_add(label_chunk_list, (void *)new_label_chunk); + + /* let's update current if needed */ + if (!current_label_chunk) + current_label_chunk = listtail(label_chunk_list); + + return 0; +} + +mpls_label_t isis_zebra_request_dynamic_label(void) +{ + struct label_chunk *label_chunk; + uint32_t i, size; + uint64_t pos; + uint32_t label = MPLS_INVALID_LABEL; + + while (current_label_chunk) { + label_chunk = listgetdata(current_label_chunk); + if (!label_chunk) + goto end; + + /* try to get next free label in currently used label chunk */ + size = label_chunk->end - label_chunk->start + 1; + for (i = 0, pos = 1; i < size; i++, pos <<= 1) { + if (!(pos & label_chunk->used_mask)) { + label_chunk->used_mask |= pos; + label = label_chunk->start + i; + goto end; + } + } + current_label_chunk = listnextnode(current_label_chunk); + } + +end: + /* + * we moved till the last chunk, or were not able to find a label, so + * let's ask for another one. + */ + if (!current_label_chunk + || current_label_chunk == listtail(label_chunk_list) + || label == MPLS_INVALID_LABEL) { + if (isis_zebra_get_label_chunk() != 0) + zlog_warn("%s: error getting label chunk!", __func__); + } + + return label; +} + +static void isis_zebra_del_label_chunk(void *val) +{ + free(val); +} + +static int isis_zebra_release_label_chunk(uint32_t start, uint32_t end) +{ + int ret; + + ret = lm_release_label_chunk(zclient_sync, start, end); + if (ret < 0) { + zlog_warn("%s: error releasing label chunk!", __func__); + return -1; + } + + return 0; +} + +void isis_zebra_release_dynamic_label(mpls_label_t label) +{ + struct listnode *node; + struct label_chunk *label_chunk; + uint64_t pos; + + for (ALL_LIST_ELEMENTS_RO(label_chunk_list, node, label_chunk)) { + if (!(label <= label_chunk->end && label >= label_chunk->start)) + continue; + + pos = 1ULL << (label - label_chunk->start); + label_chunk->used_mask &= ~pos; + + /* + * If nobody is using this chunk and it's not + * current_label_chunk, then free it. + */ + if (!label_chunk->used_mask && (current_label_chunk != node)) { + if (isis_zebra_release_label_chunk(label_chunk->start, + label_chunk->end) + != 0) + zlog_warn("%s: error releasing label chunk!", + __func__); + else { + listnode_delete(label_chunk_list, label_chunk); + isis_zebra_del_label_chunk(label_chunk); + } + } + break; + } +} + +static void isis_zebra_label_manager_connect(void) +{ + /* Connect to label manager. */ + while (zclient_socket_connect(zclient_sync) < 0) { + zlog_warn("%s: error connecting synchronous zclient!", + __func__); + sleep(1); + } + set_nonblocking(zclient_sync->sock); + while (lm_label_manager_connect(zclient_sync, 0) != 0) { + zlog_warn("%s: error connecting to label manager!", __func__); + sleep(1); + } + + label_chunk_list = list_new(); + label_chunk_list->del = isis_zebra_del_label_chunk; + while (isis_zebra_get_label_chunk() != 0) { + zlog_warn("%s: error getting first label chunk!", __func__); + sleep(1); + } +} + static void isis_zebra_connected(struct zclient *zclient) { zclient_send_reg_requests(zclient, VRF_DEFAULT); } -void isis_zebra_init(struct thread_master *master) +void isis_zebra_init(struct thread_master *master, int instance) { + /* Initialize asynchronous zclient. */ zclient = zclient_new(master, &zclient_options_default); zclient_init(zclient, PROTO_TYPE, 0, &isisd_privs); zclient->zebra_connected = isis_zebra_connected; @@ -319,7 +588,12 @@ void isis_zebra_init(struct thread_master *master) zclient->redistribute_route_add = isis_zebra_read; zclient->redistribute_route_del = isis_zebra_read; - return; + /* Initialize special zclient for synchronous message exchanges. */ + zclient_sync = zclient_new(master, &zclient_options_default); + zclient_sync->sock = -1; + zclient_sync->redist_default = ZEBRA_ROUTE_ISIS; + zclient_sync->instance = instance; + zclient_sync->privs = &isisd_privs; } void isis_zebra_stop(void) diff --git a/isisd/isis_zebra.h b/isisd/isis_zebra.h index d00f348c8..cca2b0881 100644 --- a/isisd/isis_zebra.h +++ b/isisd/isis_zebra.h @@ -24,10 +24,18 @@ extern struct zclient *zclient; -void isis_zebra_init(struct thread_master *); +struct label_chunk { + uint32_t start; + uint32_t end; + uint64_t used_mask; +}; +#define CHUNK_SIZE 64 + +void isis_zebra_init(struct thread_master *master, int instance); void isis_zebra_stop(void); struct isis_route_info; +struct sr_prefix; void isis_zebra_route_add_route(struct prefix *prefix, struct prefix_ipv6 *src_p, @@ -35,8 +43,14 @@ void isis_zebra_route_add_route(struct prefix *prefix, void isis_zebra_route_del_route(struct prefix *prefix, struct prefix_ipv6 *src_p, struct isis_route_info *route_info); +void isis_zebra_install_prefix_sid(const struct sr_prefix *srp); +void isis_zebra_uninstall_prefix_sid(const struct sr_prefix *srp); int isis_distribute_list_update(int routetype); void isis_zebra_redistribute_set(afi_t afi, int type); void isis_zebra_redistribute_unset(afi_t afi, int type); +int isis_zebra_request_label_range(uint32_t base, uint32_t chunk_size); +void isis_zebra_release_label_range(uint32_t start, uint32_t end); +mpls_label_t isis_zebra_request_dynamic_label(void); +void isis_zebra_release_dynamic_label(mpls_label_t label); #endif /* _ZEBRA_ISIS_ZEBRA_H */ diff --git a/isisd/isisd.c b/isisd/isisd.c index 2a8c503ae..caf50addd 100644 --- a/isisd/isisd.c +++ b/isisd/isisd.c @@ -56,6 +56,7 @@ #include "isisd/isis_events.h" #include "isisd/isis_te.h" #include "isisd/isis_mt.h" +#include "isisd/isis_sr.h" #include "isisd/fabricd.h" #include "isisd/isis_nb.h" @@ -128,6 +129,8 @@ struct isis_area *isis_area_create(const char *area_tag) thread_add_timer(master, lsp_tick, area, 1, &area->t_tick); flags_initialize(&area->flags); + isis_sr_area_init(area); + /* * Default values */ @@ -271,6 +274,8 @@ int isis_area_destroy(const char *area_tag) isis_area_invalidate_routes(area, area->is_type); isis_area_verify_routes(area); + isis_sr_area_term(area); + spftree_area_del(area); THREAD_TIMER_OFF(area->spf_timer[0]); @@ -748,6 +753,9 @@ void print_debug(struct vty *vty, int flags, int onoff) onoffs); if (flags & DEBUG_SPF_EVENTS) vty_out(vty, "IS-IS SPF events debugging is %s\n", onoffs); + if (flags & DEBUG_SR) + vty_out(vty, "IS-IS Segment Routing events debugging is %s\n", + onoffs); if (flags & DEBUG_UPDATE_PACKETS) vty_out(vty, "IS-IS Update related packet debugging is %s\n", onoffs); @@ -812,6 +820,10 @@ static int config_write_debug(struct vty *vty) vty_out(vty, "debug " PROTO_NAME " spf-events\n"); write++; } + if (flags & DEBUG_SR) { + vty_out(vty, "debug " PROTO_NAME " sr-events\n"); + write++; + } if (flags & DEBUG_UPDATE_PACKETS) { vty_out(vty, "debug " PROTO_NAME " update-packets\n"); write++; @@ -1011,6 +1023,33 @@ DEFUN (no_debug_isis_spfevents, return CMD_SUCCESS; } +DEFUN (debug_isis_srevents, + debug_isis_srevents_cmd, + "debug " PROTO_NAME " sr-events", + DEBUG_STR + PROTO_HELP + "IS-IS Segment Routing Events\n") +{ + isis->debugs |= DEBUG_SR; + print_debug(vty, DEBUG_SR, 1); + + return CMD_SUCCESS; +} + +DEFUN (no_debug_isis_srevents, + no_debug_isis_srevents_cmd, + "no debug " PROTO_NAME " sr-events", + NO_STR + UNDEBUG_STR + PROTO_HELP + "IS-IS Segment Routing Events\n") +{ + isis->debugs &= ~DEBUG_SR; + print_debug(vty, DEBUG_SR, 0); + + return CMD_SUCCESS; +} + DEFUN (debug_isis_rtevents, debug_isis_rtevents_cmd, "debug " PROTO_NAME " route-events", @@ -2198,6 +2237,8 @@ void isis_init(void) install_element(ENABLE_NODE, &no_debug_isis_upd_cmd); install_element(ENABLE_NODE, &debug_isis_spfevents_cmd); install_element(ENABLE_NODE, &no_debug_isis_spfevents_cmd); + install_element(ENABLE_NODE, &debug_isis_srevents_cmd); + install_element(ENABLE_NODE, &no_debug_isis_srevents_cmd); install_element(ENABLE_NODE, &debug_isis_rtevents_cmd); install_element(ENABLE_NODE, &no_debug_isis_rtevents_cmd); install_element(ENABLE_NODE, &debug_isis_events_cmd); @@ -2223,6 +2264,8 @@ void isis_init(void) install_element(CONFIG_NODE, &no_debug_isis_upd_cmd); install_element(CONFIG_NODE, &debug_isis_spfevents_cmd); install_element(CONFIG_NODE, &no_debug_isis_spfevents_cmd); + install_element(CONFIG_NODE, &debug_isis_srevents_cmd); + install_element(CONFIG_NODE, &no_debug_isis_srevents_cmd); install_element(CONFIG_NODE, &debug_isis_rtevents_cmd); install_element(CONFIG_NODE, &no_debug_isis_rtevents_cmd); install_element(CONFIG_NODE, &debug_isis_events_cmd); diff --git a/isisd/isisd.h b/isisd/isisd.h index 53776b2ec..56ea0993f 100644 --- a/isisd/isisd.h +++ b/isisd/isisd.h @@ -30,6 +30,7 @@ #include "isisd/isis_redist.h" #include "isisd/isis_pdu_counter.h" #include "isisd/isis_circuit.h" +#include "isisd/isis_sr.h" #include "isis_flags.h" #include "isis_lsp.h" #include "isis_memory.h" @@ -165,6 +166,8 @@ struct isis_area { struct list *mt_settings; /* MPLS-TE settings */ struct mpls_te_area *mta; + /* Segment Routing information */ + struct isis_sr_db srdb; int ipv6_circuits; bool purge_originator; /* Counters */ @@ -218,6 +221,10 @@ int isis_area_passwd_cleartext_set(struct isis_area *area, int level, int isis_area_passwd_hmac_md5_set(struct isis_area *area, int level, const char *passwd, uint8_t snp_auth); +/* YANG paths */ +#define ISIS_INSTANCE "/frr-isisd:isis/instance" +#define ISIS_SR "/frr-isisd:isis/instance/segment-routing" + /* Master of threads. */ extern struct thread_master *master; @@ -233,6 +240,7 @@ extern struct thread_master *master; #define DEBUG_FLOODING (1<<9) #define DEBUG_BFD (1<<10) #define DEBUG_TX_QUEUE (1<<11) +#define DEBUG_SR (1<<12) #define lsp_debug(...) \ do { \ diff --git a/isisd/subdir.am b/isisd/subdir.am index 94f9116d3..9e855ad8c 100644 --- a/isisd/subdir.am +++ b/isisd/subdir.am @@ -11,6 +11,7 @@ vtysh_scan += \ isisd/isis_redist.c \ isisd/isis_spf.c \ isisd/isis_te.c \ + isisd/isis_sr.c \ isisd/isis_vty_fabricd.c \ isisd/isisd.c \ # end @@ -48,6 +49,7 @@ noinst_HEADERS += \ isisd/isis_routemap.h \ isisd/isis_spf.h \ isisd/isis_spf_private.h \ + isisd/isis_sr.h \ isisd/isis_te.h \ isisd/isis_tlvs.h \ isisd/isis_tx_queue.h \ @@ -77,6 +79,7 @@ LIBISIS_SOURCES = \ isisd/isis_route.c \ isisd/isis_routemap.c \ isisd/isis_spf.c \ + isisd/isis_sr.c \ isisd/isis_te.c \ isisd/isis_tlvs.c \ isisd/isis_tx_queue.c \ diff --git a/lib/mpls.h b/lib/mpls.h index 05cf2935e..126dbf753 100644 --- a/lib/mpls.h +++ b/lib/mpls.h @@ -127,7 +127,8 @@ enum lsp_types_t { ZEBRA_LSP_LDP = 2, /* LDP LSP. */ ZEBRA_LSP_BGP = 3, /* BGP LSP. */ ZEBRA_LSP_OSPF_SR = 4,/* OSPF Segment Routing LSP. */ - ZEBRA_LSP_SHARP = 5, /* Identifier for test protocol */ + ZEBRA_LSP_ISIS_SR = 5,/* IS-IS Segment Routing LSP. */ + ZEBRA_LSP_SHARP = 6, /* Identifier for test protocol */ }; /* Functions for basic label operations. */ diff --git a/zebra/zebra_mpls.h b/zebra/zebra_mpls.h index 33cb61434..e468fb9c1 100644 --- a/zebra/zebra_mpls.h +++ b/zebra/zebra_mpls.h @@ -430,6 +430,7 @@ static inline uint8_t lsp_distance(enum lsp_types_t type) case ZEBRA_LSP_NONE: case ZEBRA_LSP_SHARP: case ZEBRA_LSP_OSPF_SR: + case ZEBRA_LSP_ISIS_SR: return 150; } @@ -457,6 +458,8 @@ static inline enum lsp_types_t lsp_type_from_re_type(int re_type) return ZEBRA_LSP_BGP; case ZEBRA_ROUTE_OSPF: return ZEBRA_LSP_OSPF_SR; + case ZEBRA_ROUTE_ISIS: + return ZEBRA_LSP_ISIS_SR; case ZEBRA_ROUTE_SHARP: return ZEBRA_LSP_SHARP; default: @@ -478,6 +481,8 @@ static inline int re_type_from_lsp_type(enum lsp_types_t lsp_type) return ZEBRA_ROUTE_BGP; case ZEBRA_LSP_OSPF_SR: return ZEBRA_ROUTE_OSPF; + case ZEBRA_LSP_ISIS_SR: + return ZEBRA_ROUTE_ISIS; case ZEBRA_LSP_NONE: return ZEBRA_ROUTE_KERNEL; case ZEBRA_LSP_SHARP: @@ -505,6 +510,8 @@ static inline const char *nhlfe_type2str(enum lsp_types_t lsp_type) return "BGP"; case ZEBRA_LSP_OSPF_SR: return "SR (OSPF)"; + case ZEBRA_LSP_ISIS_SR: + return "SR (IS-IS)"; case ZEBRA_LSP_SHARP: return "SHARP"; case ZEBRA_LSP_NONE: |