From 71206282ad99473df6afd1bc083f07708fa4ec1a Mon Sep 17 00:00:00 2001 From: Christophe Fontaine Date: Fri, 20 Dec 2024 07:38:35 +0000 Subject: [PATCH] ip6: router advertisement configuration Add configuration knobs for RA messages. Instead of a hardcoded perdiodic message, allow a per-interface configuration. Signed-off-by: Christophe Fontaine --- modules/ip6/api/gr_ip6.h | 26 ++++ modules/ip6/cli/ip.h | 3 + modules/ip6/cli/meson.build | 1 + modules/ip6/cli/router_advertisement.c | 140 +++++++++++++++++ modules/ip6/control/router_advertisement.c | 166 ++++++++++++++++----- 5 files changed, 302 insertions(+), 34 deletions(-) create mode 100644 modules/ip6/cli/router_advertisement.c diff --git a/modules/ip6/api/gr_ip6.h b/modules/ip6/api/gr_ip6.h index 01137176..910a1efc 100644 --- a/modules/ip6/api/gr_ip6.h +++ b/modules/ip6/api/gr_ip6.h @@ -132,9 +132,17 @@ struct gr_ip6_addr_list_resp { struct gr_ip6_ifaddr addrs[/* n_addrs */]; }; +// router advertisement //////////////////////////////////////////////////////// #define GR_IP6_IFACE_RA_SET REQUEST_TYPE(GR_IP6_MODULE, 0x0030) struct gr_ip6_ra_set_req { uint16_t iface_id; + uint16_t set_interval : 1; + uint16_t set_lifetime : 1; + uint16_t set_preference : 1; + + uint16_t interval; + uint16_t lifetime; + uint16_t preference; }; // struct gr_ip6_ra_set_resp { }; @@ -143,4 +151,22 @@ struct gr_ip6_ra_clear_req { uint16_t iface_id; }; // struct gr_ip6_ra_clear_resp { }; + +#define GR_IP6_IFACE_RA_SHOW REQUEST_TYPE(GR_IP6_MODULE, 0x0032) +struct gr_ip6_ra_show_req { + uint16_t iface_id; +}; + +struct gr_ip6_ra_conf { + bool enabled; + uint16_t iface_id; + uint16_t interval; + uint16_t lifetime; + uint16_t preference; +}; + +struct gr_ip6_ra_show_resp { + uint16_t n_ras; + struct gr_ip6_ra_conf ras[]; +}; #endif diff --git a/modules/ip6/cli/ip.h b/modules/ip6/cli/ip.h index e99433ec..7cc04474 100644 --- a/modules/ip6/cli/ip.h +++ b/modules/ip6/cli/ip.h @@ -9,5 +9,8 @@ #define IP6_ADD_CTX(root) CLI_CONTEXT(root, CTX_ADD, CTX_ARG("ip6", "Create IPv6 stack elements.")) #define IP6_DEL_CTX(root) CLI_CONTEXT(root, CTX_DEL, CTX_ARG("ip6", "Delete IPv6 stack elements.")) #define IP6_SHOW_CTX(root) CLI_CONTEXT(root, CTX_SHOW, CTX_ARG("ip6", "Show IPv6 stack details.")) +#define IP6_SET_CTX(root) CLI_CONTEXT(root, CTX_SET, CTX_ARG("ip6", "Set IPv6 stack elements.")) +#define IP6_CLEAR_CTX(root) \ + CLI_CONTEXT(root, CTX_CLEAR, CTX_ARG("ip6", "Clear IPv6 stack elements.")) #endif diff --git a/modules/ip6/cli/meson.build b/modules/ip6/cli/meson.build index 69c41354..6068b533 100644 --- a/modules/ip6/cli/meson.build +++ b/modules/ip6/cli/meson.build @@ -5,4 +5,5 @@ cli_src += files( 'address.c', 'nexthop.c', 'route.c', + 'router_advertisement.c', ) diff --git a/modules/ip6/cli/router_advertisement.c b/modules/ip6/cli/router_advertisement.c new file mode 100644 index 00000000..e71034f0 --- /dev/null +++ b/modules/ip6/cli/router_advertisement.c @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2024 Christophe Fontaine + +#include "ip.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +static cmd_status_t ra_show(const struct gr_api_client *c, const struct ec_pnode *p) { + struct libscols_table *table = scols_new_table(); + struct gr_ip6_ra_show_resp *resp; + struct gr_ip6_ra_show_req req; + struct gr_iface iface; + void *resp_ptr = NULL; + + if (!iface_from_name(c, arg_str(p, "IFACE"), &iface)) + req.iface_id = iface.id; + else + req.iface_id = 0; + + if (gr_api_client_send_recv(c, GR_IP6_IFACE_RA_SHOW, sizeof(req), &req, &resp_ptr) < 0) + return CMD_ERROR; + resp = resp_ptr; + + scols_table_new_column(table, "IFACE", 0, 0); + scols_table_new_column(table, "RA", 0, 0); + scols_table_new_column(table, "interval", 0, 0); + scols_table_new_column(table, "lifetime", 0, 0); + scols_table_new_column(table, "preference", 0, 0); + scols_table_set_column_separator(table, " "); + + for (uint16_t i = 0; i < resp->n_ras; i++) { + struct libscols_line *line = scols_table_new_line(table, NULL); + if (iface_from_id(c, resp->ras[i].iface_id, &iface) == 0) + scols_line_sprintf(line, 0, "%s", iface.name); + else + scols_line_sprintf(line, 0, "%u", resp->ras[i].iface_id); + scols_line_sprintf(line, 1, "%u", resp->ras[i].enabled); + scols_line_sprintf(line, 2, "%u", resp->ras[i].interval); + scols_line_sprintf(line, 3, "%u", resp->ras[i].lifetime); + scols_line_sprintf(line, 4, "%u", resp->ras[i].preference); + } + + scols_print_table(table); + scols_unref_table(table); + free(resp_ptr); + return CMD_SUCCESS; +} + +static cmd_status_t ra_set(const struct gr_api_client *c, const struct ec_pnode *p) { + struct gr_ip6_ra_set_req req = {0}; + struct gr_iface iface; + + if (iface_from_name(c, arg_str(p, "IFACE"), &iface) < 0) + return CMD_ERROR; + + req.iface_id = iface.id; + if (!arg_u16(p, "IT", &req.interval)) + req.set_interval = 1; + + if (!arg_u16(p, "LT", &req.lifetime)) + req.set_lifetime = 1; + + if (!arg_u16(p, "PREF", &req.preference)) + req.set_preference = 1; + + if (gr_api_client_send_recv(c, GR_IP6_IFACE_RA_SET, sizeof(req), &req, NULL) < 0) + return CMD_ERROR; + return CMD_SUCCESS; +} + +static cmd_status_t ra_clear(const struct gr_api_client *c, const struct ec_pnode *p) { + struct gr_ip6_ra_clear_req req; + struct gr_iface iface; + + if (iface_from_name(c, arg_str(p, "IFACE"), &iface) < 0) + return CMD_ERROR; + + req.iface_id = iface.id; + if (gr_api_client_send_recv(c, GR_IP6_IFACE_RA_CLEAR, sizeof(req), &req, NULL) < 0) + return CMD_ERROR; + return CMD_SUCCESS; +} + +static int ctx_init(struct ec_node *root) { + int ret; + + ret = CLI_COMMAND( + IP6_SHOW_CTX(root), + "router-advertisment [IFACE]", + ra_show, + "Show router advertisement configuration", + with_help("Interface name.", ec_node_dyn("IFACE", complete_iface_names, NULL)) + ); + if (ret < 0) + return ret; + + ret = CLI_COMMAND( + IP6_SET_CTX(root), + "router-advertisment IFACE [interval IT] [lifetime LT] [preference PREF]", + ra_set, + "Set router advertisement parameters", + with_help("Interface name.", ec_node_dyn("IFACE", complete_iface_names, NULL)), + with_help("Interval", ec_node_uint("IT", 0, UINT16_MAX - 1, 10)), + with_help("Life time", ec_node_uint("LT", 0, UINT16_MAX - 1, 10)), + with_help("preference", ec_node_uint("PREF", 0, UINT16_MAX - 1, 10)) + ); + if (ret < 0) + return ret; + + ret = CLI_COMMAND( + IP6_CLEAR_CTX(root), + "router-advertisement IFACE", + ra_clear, + "Disable router advertisement and reset parameters", + with_help("Interface name.", ec_node_dyn("IFACE", complete_iface_names, NULL)) + ); + if (ret < 0) + return ret; + + return 0; +} + +static struct gr_cli_context ctx = { + .name = "ipv6 router-advertisement", + .init = ctx_init, +}; + +static void __attribute__((constructor, used)) init(void) { + register_context(&ctx); +} diff --git a/modules/ip6/control/router_advertisement.c b/modules/ip6/control/router_advertisement.c index 27417be3..40ad68c1 100644 --- a/modules/ip6/control/router_advertisement.c +++ b/modules/ip6/control/router_advertisement.c @@ -5,7 +5,6 @@ #include #include #include -#include #include #include #include @@ -18,33 +17,116 @@ #include #include +#define RA_DEFAULT_INTERVAL 600 +#define RA_DEFAULT_LIFETIME 1800 +#define RA_DEFAULT_PREFERENCE 40 // Medium + struct ra_ctx { struct rte_mempool *mp; - struct event *timer; }; static struct ra_ctx ra_ctx; static control_input_t ra_output; +static struct event_base *ev_base; + +struct ra_iface_conf { + struct event *timer; + bool enabled; + uint16_t interval; + uint16_t lifetime; + uint16_t preference; +}; + +static struct ra_iface_conf ra_conf[MAX_IFACES]; static struct api_out iface_ra_set(const void *request, void ** /*response*/) { const struct gr_ip6_ra_set_req *req = request; + if (iface_from_id(req->iface_id) == NULL) return api_out(errno, 0); + if (req->set_interval) + ra_conf[req->iface_id].interval = req->interval; + if (req->set_lifetime) + ra_conf[req->iface_id].lifetime = req->lifetime; + if (req->set_preference) + ra_conf[req->iface_id].preference = req->preference; + + event_add( + ra_conf[req->iface_id].timer, + &(struct timeval) {.tv_sec = ra_conf[req->iface_id].interval} + ); + event_active(ra_conf[req->iface_id].timer, 0, 0); return api_out(0, 0); } static struct api_out iface_ra_clear(const void *request, void ** /*response*/) { const struct gr_ip6_ra_clear_req *req = request; + if (iface_from_id(req->iface_id) == NULL) return api_out(errno, 0); + event_del(ra_conf[req->iface_id].timer); + ra_conf[req->iface_id].interval = RA_DEFAULT_INTERVAL; + ra_conf[req->iface_id].lifetime = RA_DEFAULT_LIFETIME; + ra_conf[req->iface_id].preference = RA_DEFAULT_PREFERENCE; + return api_out(0, 0); } +static struct api_out iface_ra_show(const void *request, void **response) { + const struct gr_ip6_ra_show_req *req = request; + struct gr_ip6_ra_show_resp *resp; + uint16_t iface_id, n_ras; + struct hoplist *addrs; + bool show_all = false; + size_t len; + + if (req->iface_id == 0) + show_all = true; + else if (iface_from_id(req->iface_id) == NULL) + return api_out(errno, 0); + + n_ras = 0; + for (iface_id = 0; iface_id < MAX_IFACES; iface_id++) { + addrs = ip6_addr_get_all(iface_id); + if (addrs == NULL || addrs->count == 0) + continue; + if (show_all == false && iface_id != req->iface_id) + continue; + n_ras++; + } + + len = sizeof(*resp) + n_ras * sizeof(struct gr_ip6_ra_conf); + resp = calloc(1, sizeof(*resp) + n_ras * sizeof(struct gr_ip6_ra_conf)); + if (!resp) + return api_out(ENOMEM, 0); + resp->n_ras = n_ras; + n_ras = 0; + for (uint16_t iface_id = 0; iface_id < MAX_IFACES; iface_id++) { + addrs = ip6_addr_get_all(iface_id); + if (addrs == NULL || addrs->count == 0) + continue; + if (show_all == false && iface_id != req->iface_id) + continue; + + resp->ras[n_ras].iface_id = iface_id; + resp->ras[n_ras].enabled = event_pending( + ra_conf[iface_id].timer, EV_TIMEOUT | EV_READ | EV_WRITE | EV_SIGNAL, 0 + ); + resp->ras[n_ras].interval = ra_conf[iface_id].interval; + resp->ras[n_ras].lifetime = ra_conf[iface_id].lifetime; + resp->ras[n_ras].preference = ra_conf[iface_id].preference; + n_ras++; + } + + *response = resp; + return api_out(0, len); +} void ndp_router_sollicit_input_cb(struct rte_mbuf *m) { + uint16_t iface_id = mbuf_data(m)->iface->id; rte_pktmbuf_free(m); - event_active(ra_ctx.timer, 0, 0); + event_active(ra_conf[iface_id].timer, 0, 0); } static void build_ra_packet(struct rte_mbuf *m, struct rte_ipv6_addr *srcv6) { @@ -91,53 +173,42 @@ static void build_ra_packet(struct rte_mbuf *m, struct rte_ipv6_addr *srcv6) { icmp6->cksum = rte_ipv6_udptcp_cksum(ip, icmp6); } -static void send_ra_cb(evutil_socket_t, short /*what*/, void * /*priv*/) { - struct iface *iface = NULL; +static void send_ra_cb(evutil_socket_t, short /*what*/, void *priv) { + struct iface *iface = priv; struct rte_mbuf *m; - while ((iface = iface_next(GR_IFACE_TYPE_UNDEF, iface)) != NULL) { - struct hoplist *hl = ip6_addr_get_all(iface->id); - if (hl == NULL) + struct hoplist *hl = ip6_addr_get_all(iface->id); + if (hl == NULL) + return; + for (unsigned i = 0; i < hl->count; i++) { + struct nexthop *nh = hl->nh[i]; + struct rte_ipv6_addr ip = nh->ipv6; + if (nh->family != AF_INET6) + continue; + if (!rte_ipv6_addr_is_linklocal(&ip)) continue; - for (unsigned i = 0; i < hl->count; i++) { - struct nexthop *nh = hl->nh[i]; - struct rte_ipv6_addr ip = nh->ipv6; - if (nh->family != AF_INET6) - continue; - if (!rte_ipv6_addr_is_linklocal(&ip)) - continue; - - if ((m = rte_pktmbuf_alloc(ra_ctx.mp)) == NULL) { - LOG(ERR, "rte_pktmbuf_alloc"); - return; - } - mbuf_data(m)->iface = iface; - build_ra_packet(m, &ip); - post_to_stack(ra_output, m); + + if ((m = rte_pktmbuf_alloc(ra_ctx.mp)) == NULL) { + LOG(ERR, "rte_pktmbuf_alloc"); + return; } + mbuf_data(m)->iface = iface; + build_ra_packet(m, &ip); + post_to_stack(ra_output, m); } } -static void ra_init(struct event_base *ev_base) { +static void ra_init(struct event_base *base) { + ev_base = base; ra_output = gr_control_input_register_handler("ip6_output", true); ra_ctx.mp = gr_pktmbuf_pool_get(SOCKET_ID_ANY, 512); if (ra_ctx.mp == NULL) { ABORT("gr_pktmbuf_pool_get ENOMEM"); } - - ra_ctx.timer = event_new(ev_base, -1, EV_PERSIST | EV_FINALIZE, send_ra_cb, NULL); - if (ra_ctx.timer == NULL) { - ABORT("event_new() failed"); - } - - if (event_add(ra_ctx.timer, &(struct timeval) {.tv_sec = 600}) < 0) { - ABORT("event_add() failed"); - } } static void ra_fini(struct event_base * /*ev_base*/) { gr_pktmbuf_pool_release(ra_ctx.mp, 512); - event_free(ra_ctx.timer); } static struct gr_module ra_module = { @@ -159,8 +230,35 @@ static struct gr_api_handler ra_clear_handler = { .callback = iface_ra_clear, }; +static struct gr_api_handler ra_show_handler = { + .name = "show interface ra", + .request_type = GR_IP6_IFACE_RA_SHOW, + .callback = iface_ra_show, +}; + +static void iface_event_handler(iface_event_t event, struct iface *iface) { + if (event == IFACE_EVENT_POST_ADD) { + ra_conf[iface->id].interval = RA_DEFAULT_INTERVAL; + ra_conf[iface->id].lifetime = RA_DEFAULT_LIFETIME; + ra_conf[iface->id].preference = RA_DEFAULT_PREFERENCE; + + ra_conf[iface->id].timer = event_new( + ev_base, -1, EV_PERSIST | EV_FINALIZE, send_ra_cb, iface + ); + } else if (event == IFACE_EVENT_PRE_REMOVE) { + event_free(ra_conf[iface->id].timer); + ra_conf[iface->id].timer = NULL; + } +} + +static struct iface_event_handler iface_event_address_handler = { + .callback = iface_event_handler, +}; + RTE_INIT(router_advertisement_init) { gr_register_module(&ra_module); gr_register_api_handler(&ra_set_handler); gr_register_api_handler(&ra_clear_handler); + gr_register_api_handler(&ra_show_handler); + iface_event_register_handler(&iface_event_address_handler); }