// SPDX-License-Identifier: GPL-2.0-or-later /* * BFD daemon code * Copyright (C) 2018 Network Device Education Foundation, Inc. ("NetDEF") */ #include #include #include #include #include #include #include "filter.h" #include "if.h" #include "vrf.h" #include "bfd.h" #include "bfdd_nb.h" #include "bfddp_packet.h" #include "lib/version.h" #include "lib/command.h" /* * FRR related code. */ DEFINE_MGROUP(BFDD, "Bidirectional Forwarding Detection Daemon"); DEFINE_MTYPE(BFDD, BFDD_CONTROL, "control socket memory"); DEFINE_MTYPE(BFDD, BFDD_NOTIFICATION, "control notification data"); /* Master of threads. */ struct event_loop *master; /* BFDd privileges */ static zebra_capabilities_t _caps_p[] = {ZCAP_BIND, ZCAP_SYS_ADMIN, ZCAP_NET_RAW}; /* BFD daemon information. */ static struct frr_daemon_info bfdd_di; void socket_close(int *s) { if (*s <= 0) return; if (close(*s) != 0) zlog_err("%s: close(%d): (%d) %s", __func__, *s, errno, strerror(errno)); *s = -1; } static void sigusr1_handler(void) { zlog_rotate(); } static void sigterm_handler(void) { bglobal.bg_shutdown = true; /* Signalize shutdown. */ frr_early_fini(); /* Stop receiving message from zebra. */ bfdd_zclient_stop(); /* Shutdown controller to avoid receiving anymore commands. */ control_shutdown(); /* Shutdown and free all protocol related memory. */ bfd_shutdown(); bfd_vrf_terminate(); /* Terminate and free() FRR related memory. */ frr_fini(); exit(0); } static void sighup_handler(void) { zlog_info("SIGHUP received"); /* Reload config file. */ vty_read_config(NULL, bfdd_di.config_file, config_default); } static struct frr_signal_t bfd_signals[] = { { .signal = SIGUSR1, .handler = &sigusr1_handler, }, { .signal = SIGTERM, .handler = &sigterm_handler, }, { .signal = SIGINT, .handler = &sigterm_handler, }, { .signal = SIGHUP, .handler = &sighup_handler, }, }; static const struct frr_yang_module_info *const bfdd_yang_modules[] = { &frr_filter_info, &frr_interface_info, &frr_bfdd_info, &frr_vrf_info, }; FRR_DAEMON_INFO(bfdd, BFD, .vty_port = 2617, .proghelp = "Implementation of the BFD protocol.", .signals = bfd_signals, .n_signals = array_size(bfd_signals), .privs = &bglobal.bfdd_privs, .yang_modules = bfdd_yang_modules, .n_yang_modules = array_size(bfdd_yang_modules), ); #define OPTION_CTLSOCK 1001 #define OPTION_DPLANEADDR 2000 static const struct option longopts[] = { {"bfdctl", required_argument, NULL, OPTION_CTLSOCK}, {"dplaneaddr", required_argument, NULL, OPTION_DPLANEADDR}, {0} }; /* * BFD daemon related code. */ struct bfd_global bglobal; const struct bfd_diag_str_list diag_list[] = { {.str = "control-expired", .type = BD_CONTROL_EXPIRED}, {.str = "echo-failed", .type = BD_ECHO_FAILED}, {.str = "neighbor-down", .type = BD_NEIGHBOR_DOWN}, {.str = "forwarding-reset", .type = BD_FORWARDING_RESET}, {.str = "path-down", .type = BD_PATH_DOWN}, {.str = "concatenated-path-down", .type = BD_CONCATPATH_DOWN}, {.str = "administratively-down", .type = BD_ADMIN_DOWN}, {.str = "reverse-concat-path-down", .type = BD_REVCONCATPATH_DOWN}, {.str = NULL}, }; const struct bfd_state_str_list state_list[] = { {.str = "admin-down", .type = PTM_BFD_ADM_DOWN}, {.str = "down", .type = PTM_BFD_DOWN}, {.str = "init", .type = PTM_BFD_INIT}, {.str = "up", .type = PTM_BFD_UP}, {.str = NULL}, }; static uint16_t parse_port(const char *str) { char *nulbyte; long rv; errno = 0; rv = strtol(str, &nulbyte, 10); /* No conversion performed. */ if (rv == 0 && errno == EINVAL) { fprintf(stderr, "invalid BFD data plane address port: %s\n", str); exit(0); } /* Invalid number range. */ if ((rv <= 0 || rv >= 65535) || errno == ERANGE) { fprintf(stderr, "invalid BFD data plane port range: %s\n", str); exit(0); } /* There was garbage at the end of the string. */ if (*nulbyte != 0) { fprintf(stderr, "invalid BFD data plane port: %s\n", str); exit(0); } return (uint16_t)rv; } static void distributed_bfd_init(const char *arg) { char *sptr, *saux; bool is_client = false; size_t slen; socklen_t salen; char addr[64]; char type[64]; union { struct sockaddr_in sin; struct sockaddr_in6 sin6; struct sockaddr_un sun; } sa; /* Basic parsing: find ':' to figure out type part and address part. */ sptr = strchr(arg, ':'); if (sptr == NULL) { fprintf(stderr, "invalid BFD data plane socket: %s\n", arg); exit(1); } /* Calculate type string length. */ slen = (size_t)(sptr - arg); /* Copy the address part. */ sptr++; strlcpy(addr, sptr, sizeof(addr)); /* Copy type part. */ strlcpy(type, arg, slen + 1); /* Reset address data. */ memset(&sa, 0, sizeof(sa)); /* Fill the address information. */ if (strcmp(type, "unix") == 0 || strcmp(type, "unixc") == 0) { if (strcmp(type, "unixc") == 0) is_client = true; salen = sizeof(sa.sun); sa.sun.sun_family = AF_UNIX; strlcpy(sa.sun.sun_path, addr, sizeof(sa.sun.sun_path)); } else if (strcmp(type, "ipv4") == 0 || strcmp(type, "ipv4c") == 0) { if (strcmp(type, "ipv4c") == 0) is_client = true; salen = sizeof(sa.sin); sa.sin.sin_family = AF_INET; /* Parse port if any. */ sptr = strchr(addr, ':'); if (sptr == NULL) { sa.sin.sin_port = htons(BFD_DATA_PLANE_DEFAULT_PORT); } else { *sptr = 0; sa.sin.sin_port = htons(parse_port(sptr + 1)); } if (inet_pton(AF_INET, addr, &sa.sin.sin_addr) != 1) errx(1, "%s: inet_pton: invalid address %s", __func__, addr); } else if (strcmp(type, "ipv6") == 0 || strcmp(type, "ipv6c") == 0) { if (strcmp(type, "ipv6c") == 0) is_client = true; salen = sizeof(sa.sin6); sa.sin6.sin6_family = AF_INET6; /* Check for IPv6 enclosures '[]' */ sptr = &addr[0]; if (*sptr != '[') errx(1, "%s: invalid IPv6 address format: %s", __func__, addr); saux = strrchr(addr, ']'); if (saux == NULL) errx(1, "%s: invalid IPv6 address format: %s", __func__, addr); /* Consume the '[]:' part. */ slen = saux - sptr; memmove(addr, addr + 1, slen); addr[slen - 1] = 0; /* Parse port if any. */ saux++; sptr = strrchr(saux, ':'); if (sptr == NULL) { sa.sin6.sin6_port = htons(BFD_DATA_PLANE_DEFAULT_PORT); } else { *sptr = 0; sa.sin6.sin6_port = htons(parse_port(sptr + 1)); } if (inet_pton(AF_INET6, addr, &sa.sin6.sin6_addr) != 1) errx(1, "%s: inet_pton: invalid address %s", __func__, addr); } else { fprintf(stderr, "invalid BFD data plane socket type: %s\n", type); exit(1); } /* Initialize BFD data plane listening socket. */ bfd_dplane_init((struct sockaddr *)&sa, salen, is_client); } static void bg_init(void) { struct zebra_privs_t bfdd_privs = { #if defined(FRR_USER) && defined(FRR_GROUP) .user = FRR_USER, .group = FRR_GROUP, #endif #if defined(VTY_GROUP) .vty_group = VTY_GROUP, #endif .caps_p = _caps_p, .cap_num_p = array_size(_caps_p), .cap_num_i = 0, }; TAILQ_INIT(&bglobal.bg_bcslist); TAILQ_INIT(&bglobal.bg_obslist); memcpy(&bglobal.bfdd_privs, &bfdd_privs, sizeof(bfdd_privs)); } int main(int argc, char *argv[]) { char ctl_path[512], dplane_addr[512]; bool ctlsockused = false; int opt; bglobal.bg_use_dplane = false; /* Initialize system sockets. */ bg_init(); frr_preinit(&bfdd_di, argc, argv); frr_opt_add("", longopts, " --bfdctl Specify bfdd control socket\n" " --dplaneaddr Specify BFD data plane address\n"); snprintf(ctl_path, sizeof(ctl_path), BFDD_CONTROL_SOCKET, "", ""); while (true) { opt = frr_getopt(argc, argv, NULL); if (opt == EOF) break; switch (opt) { case OPTION_CTLSOCK: strlcpy(ctl_path, optarg, sizeof(ctl_path)); ctlsockused = true; break; case OPTION_DPLANEADDR: strlcpy(dplane_addr, optarg, sizeof(dplane_addr)); bglobal.bg_use_dplane = true; break; default: frr_help_exit(1); } } if (bfdd_di.pathspace && !ctlsockused) snprintf(ctl_path, sizeof(ctl_path), BFDD_CONTROL_SOCKET, "/", bfdd_di.pathspace); /* Initialize FRR infrastructure. */ master = frr_init(); /* Initialize control socket. */ control_init(ctl_path); /* Initialize BFD data structures. */ bfd_initialize(); bfd_vrf_init(); access_list_init(); /* Initialize zebra connection. */ bfdd_zclient_init(&bglobal.bfdd_privs); event_add_read(master, control_accept, NULL, bglobal.bg_csock, &bglobal.bg_csockev); /* Install commands. */ bfdd_vty_init(); /* read configuration file and daemonize */ frr_config_fork(); /* Initialize BFD data plane listening socket. */ if (bglobal.bg_use_dplane) distributed_bfd_init(dplane_addr); frr_run(master); /* NOTREACHED */ return 0; }