├── .clang-format ├── .codespellrc ├── .containerignore ├── .dockerignore ├── .editorconfig ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ └── feature_request.yml └── workflows │ ├── check.yml │ └── publish.yml ├── .gitignore ├── .lsan-suppressions ├── .mailmap ├── CONTRIBUTING.md ├── Containerfile ├── GNUmakefile ├── README.md ├── api ├── gr_api.h ├── gr_api_client_impl.h ├── gr_bitops.h ├── gr_clock.h ├── gr_errno.h ├── gr_macro.h ├── gr_net_compat.h ├── gr_net_types.h ├── gr_string.h ├── meson.build ├── printf.c ├── printf_test.c ├── string.c └── string_test.c ├── cli ├── complete.c ├── complete.h ├── ec_node_devargs.c ├── ec_node_dyn.c ├── ecoli.c ├── exec.c ├── exec.h ├── gr_cli.h ├── gr_table.h ├── grcli.bash-completion ├── interact.c ├── interact.h ├── log.c ├── log.h ├── main.c ├── meson.build ├── quit.c └── table.c ├── debian ├── control ├── grout-dev.install ├── grout.bash-completion ├── grout.conffiles ├── grout.install ├── grout.service └── rules ├── devtools ├── check-patches ├── check-whitespace ├── commit-msg ├── cross │ └── aarch64.ini ├── git-stats └── gpg-signing-key.asc ├── docs ├── graph.svg ├── grcli.1.md ├── grout.8.md ├── logo-white.svg ├── logo.svg └── meson.build ├── frr ├── frr_plugin_install.sh ├── if_grout.c ├── if_grout.h ├── log_grout.h ├── meson.build ├── rt_grout.c ├── rt_grout.h ├── zebra_dplane_grout.c └── zebra_dplane_grout.h ├── licenses ├── BSD-3-clause.txt └── GPL-2.0-or-later.txt ├── main ├── api.c ├── api.h ├── dpdk.c ├── dpdk.h ├── event.c ├── gr_cmocka.h ├── gr_config.h ├── gr_event.h ├── gr_log.h ├── gr_module.h ├── gr_queue.h ├── gr_vec.h ├── grout.bash-completion ├── grout.default ├── grout.init ├── grout.service ├── main.c ├── meson.build ├── module.c ├── module.h ├── sd_notify.c ├── sd_notify.h ├── signals.c ├── signals.h └── vec_test.c ├── meson.build ├── meson_options.txt ├── modules ├── infra │ ├── api │ │ ├── affinity.c │ │ ├── gr_infra.h │ │ ├── gr_nexthop.h │ │ ├── graph.c │ │ ├── iface.c │ │ ├── meson.build │ │ ├── nexthop.c │ │ ├── stats.c │ │ └── trace.c │ ├── cli │ │ ├── affinity.c │ │ ├── events.c │ │ ├── gr_cli_iface.h │ │ ├── graph.c │ │ ├── iface.c │ │ ├── loopback.c │ │ ├── meson.build │ │ ├── nexthop.c │ │ ├── port.c │ │ ├── stats.c │ │ ├── trace.c │ │ └── vlan.c │ ├── control │ │ ├── control_output.c │ │ ├── gr_graph.h │ │ ├── gr_iface.h │ │ ├── gr_loopback.h │ │ ├── gr_mempool.h │ │ ├── gr_nh_control.h │ │ ├── gr_port.h │ │ ├── gr_vlan.h │ │ ├── gr_worker.h │ │ ├── graph.c │ │ ├── graph_priv.h │ │ ├── iface.c │ │ ├── loopback.c │ │ ├── mempool.c │ │ ├── meson.build │ │ ├── nexthop.c │ │ ├── port.c │ │ ├── vlan.c │ │ ├── vrf.c │ │ ├── worker.c │ │ ├── worker_priv.h │ │ ├── worker_test.c │ │ └── xconnect.c │ ├── datapath │ │ ├── control_input.c │ │ ├── control_output.c │ │ ├── drop.c │ │ ├── eth_input.c │ │ ├── eth_output.c │ │ ├── gr_control_input.h │ │ ├── gr_control_output.h │ │ ├── gr_datapath.h │ │ ├── gr_eth.h │ │ ├── gr_icmp6.h │ │ ├── gr_mbuf.h │ │ ├── gr_rxtx.h │ │ ├── gr_trace.h │ │ ├── l1_xconnect.c │ │ ├── loop_input.c │ │ ├── loop_output.c │ │ ├── main_loop.c │ │ ├── meson.build │ │ ├── rx.c │ │ ├── trace.c │ │ └── tx.c │ └── meson.build ├── ip │ ├── api │ │ ├── gr_ip4.h │ │ └── meson.build │ ├── cli │ │ ├── address.c │ │ ├── icmp.c │ │ ├── ip.h │ │ ├── meson.build │ │ ├── nexthop.c │ │ └── route.c │ ├── control │ │ ├── address.c │ │ ├── gr_ip4_control.h │ │ ├── icmp.c │ │ ├── meson.build │ │ ├── nexthop.c │ │ └── route.c │ ├── datapath │ │ ├── arp_input.c │ │ ├── arp_input_reply.c │ │ ├── arp_input_request.c │ │ ├── arp_output_reply.c │ │ ├── arp_output_request.c │ │ ├── fib4.c │ │ ├── gr_fib4.h │ │ ├── gr_ip4_datapath.h │ │ ├── icmp_input.c │ │ ├── icmp_local_send.c │ │ ├── icmp_output.c │ │ ├── ip_error.c │ │ ├── ip_forward.c │ │ ├── ip_hold.c │ │ ├── ip_input.c │ │ ├── ip_local.c │ │ ├── ip_output.c │ │ └── meson.build │ └── meson.build ├── ip6 │ ├── api │ │ ├── gr_ip6.h │ │ └── meson.build │ ├── cli │ │ ├── address.c │ │ ├── icmp6.c │ │ ├── ip.h │ │ ├── meson.build │ │ ├── nexthop.c │ │ ├── route.c │ │ └── router_advert.c │ ├── control │ │ ├── address.c │ │ ├── gr_ip6_control.h │ │ ├── icmp6.c │ │ ├── meson.build │ │ ├── nexthop.c │ │ ├── route.c │ │ └── router_advert.c │ ├── datapath │ │ ├── fib6.c │ │ ├── gr_fib6.h │ │ ├── gr_ip6_datapath.h │ │ ├── icmp6_input.c │ │ ├── icmp6_local_send.c │ │ ├── icmp6_output.c │ │ ├── ip6_error.c │ │ ├── ip6_forward.c │ │ ├── ip6_hold.c │ │ ├── ip6_input.c │ │ ├── ip6_local.c │ │ ├── ip6_output.c │ │ ├── meson.build │ │ ├── ndp_na_input.c │ │ ├── ndp_na_output.c │ │ ├── ndp_ns_input.c │ │ ├── ndp_ns_output.c │ │ └── ndp_rs_input.c │ └── meson.build ├── ipip │ ├── cli.c │ ├── control.c │ ├── datapath_in.c │ ├── datapath_out.c │ ├── gr_ipip.h │ ├── ipip_priv.h │ └── meson.build ├── l4 │ ├── gr_l4.h │ ├── l4_input_local.c │ ├── l4_loopback_output.c │ └── meson.build ├── meson.build └── srv6 │ ├── cli.c │ ├── control_headend.c │ ├── control_local.c │ ├── datapath_headend.c │ ├── datapath_local.c │ ├── gr_srv6.h │ ├── meson.build │ └── srv6_priv.h ├── rpm └── grout.spec ├── smoke ├── _init.sh ├── _init_frr.sh ├── affinity_test.sh ├── config_test.sh ├── graph_svg_test.sh ├── ip6_add_del_test.sh ├── ip6_builtin_icmp_test.sh ├── ip6_forward_frr_test.sh ├── ip6_forward_test.sh ├── ip6_rs_ra_test.sh ├── ip6_same_peer_test.sh ├── ip_add_del_test.sh ├── ip_builtin_icmp_test.sh ├── ip_forward_frr_test.sh ├── ip_forward_test.sh ├── ipip_encap_test.sh ├── nexthop_ageing_test.sh ├── run.sh ├── srv6_test.sh ├── vlan_forward_test.sh └── vrf_forward_test.sh └── subprojects ├── dpdk.wrap ├── ecoli.wrap ├── frr.wrap └── packagefiles └── frr ├── lib-clippy-pointer-offsets-are-signed.patch └── meson-add-dependency-definition.patch /.codespellrc: -------------------------------------------------------------------------------- 1 | # ex: ft=dosini 2 | 3 | [codespell] 4 | quiet-level = 35 5 | skip = 6 | subprojects/*, 7 | ignore-words-list = 8 | te, 9 | -------------------------------------------------------------------------------- /.containerignore: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Copyright (c) 2023 Robin Jarry 3 | 4 | /.cache 5 | /build 6 | /debian/changelog 7 | /redhat-linux-build 8 | /subprojects/dpdk 9 | /subprojects/ecoli 10 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .containerignore -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Copyright (c) 2023 Robin Jarry 3 | # See https://editorconfig.org/ for syntax reference. 4 | 5 | root = true 6 | 7 | [*] 8 | end_of_line = lf 9 | charset = utf-8 10 | 11 | [meson.build] 12 | insert_final_newline = true 13 | trim_trailing_whitespace = true 14 | indent_style = space 15 | indent_size = 2 16 | max_line_length = 100 17 | 18 | [*.{c,h}] 19 | insert_final_newline = true 20 | trim_trailing_whitespace = true 21 | indent_style = tab 22 | tab_width = 8 23 | max_line_length = 100 24 | 25 | [*.sh] 26 | insert_final_newline = true 27 | trim_trailing_whitespace = true 28 | indent_style = tab 29 | tab_width = 8 30 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.svg -diff 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Copyright (c) 2025 Robin Jarry 3 | --- 4 | name: Bug Report 5 | description: File a bug report. 6 | type: Bug 7 | labels: ["triage"] 8 | body: 9 | - type: markdown 10 | attributes: 11 | value: > 12 | Thanks for taking the time to fill out this bug report! 13 | Make sure to check if a similar bug wasn't already reported and/or 14 | fixed by searching through existing issues and pull requests (including 15 | closed ones). 16 | - type: textarea 17 | id: description 18 | attributes: 19 | label: Description 20 | description: > 21 | Please describe what is the issue with as much detail as possible. 22 | Include any relevant logs, error messages and/or core dump backtraces. 23 | Also include what are the steps to reproduce the problem. 24 | placeholder: Bug description... 25 | validations: 26 | required: true 27 | - type: input 28 | id: version 29 | attributes: 30 | label: Version 31 | description: What version of grout is the bug observed on? 32 | placeholder: grout --version 33 | validations: 34 | required: true 35 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Copyright (c) 2025 Robin Jarry 3 | --- 4 | blank_issues_enabled: false 5 | contact_links: 6 | - name: Mailing List 7 | url: https://mails.dpdk.org/archives/grout/ 8 | about: | 9 | Please ask and answer questions on grout@dpdk.org (subscription not required). 10 | - name: Slack Channel 11 | url: https://dpdkproject.slack.com/archives/C07NAFWE1MG 12 | about: "Join us on #grout @ slack.dpdkproject.org" 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Copyright (c) 2025 Robin Jarry 3 | --- 4 | name: Feature Request 5 | description: Request for a new feature. 6 | type: Feature 7 | labels: ["triage"] 8 | body: 9 | - type: markdown 10 | attributes: 11 | value: > 12 | Thanks for taking the time to request a new feature! 13 | Make sure to check if a similar feature wasn't already requested and/or 14 | implemented by searching through existing issues and pull requests 15 | (including closed ones). 16 | - type: textarea 17 | id: description 18 | attributes: 19 | label: Description 20 | description: > 21 | Please describe what feature is missing. You can include as much detail 22 | as you want, including code snippets and external links. 23 | placeholder: Feature description... 24 | validations: 25 | required: true 26 | - type: textarea 27 | id: context 28 | attributes: 29 | label: Context and Use Cases 30 | description: > 31 | Explain why you need this feature and how it will be used. 32 | placeholder: Context and use-cases... 33 | validations: 34 | required: true 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Copyright (c) 2023 Robin Jarry 3 | 4 | /*.deb 5 | /*.list 6 | /*.rpm 7 | /.cache 8 | /build 9 | /debian/*.debhelper 10 | /debian/*.debhelper.log 11 | /debian/*.substvars 12 | /debian/.* 13 | /debian/changelog 14 | /debian/debhelper* 15 | /debian/files 16 | /debian/grout-dev/ 17 | /debian/grout/ 18 | /debian/tmp/ 19 | /redhat-linux-build 20 | /subprojects/dpdk 21 | /subprojects/ecoli 22 | /subprojects/frr 23 | -------------------------------------------------------------------------------- /.lsan-suppressions: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Copyright (c) 2024 Robin Jarry 3 | # Known wcsdup() leak in libedit 4 | # https://salsa.debian.org/debian/libedit/-/blob/upstream/3.1-20230828/src/eln.c#L227-229 5 | leak:el_set 6 | -------------------------------------------------------------------------------- /.mailmap: -------------------------------------------------------------------------------- 1 | Robin Jarry 2 | -------------------------------------------------------------------------------- /Containerfile: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Copyright (c) 2025 Christophe Fontaine 3 | 4 | FROM registry.access.redhat.com/ubi9 as ubi-builder 5 | COPY grout.*.rpm /tmp 6 | RUN mkdir -p /tmp/null 7 | RUN dnf -y install --nodocs --setopt=install_weak_deps=0 --releasever 9 --installroot /tmp/null /tmp/grout.$(arch).rpm 8 | RUN dnf -y --installroot /tmp/null clean all 9 | RUN rm -rf /tmp/null/var/cache/* /tmp/null/var/log/dnf* /tmp/null/var/log/yum.* 10 | 11 | FROM registry.access.redhat.com/ubi9-micro 12 | COPY --from=ubi-builder /tmp/null/ / 13 | CMD ["/usr/bin/grout"] 14 | -------------------------------------------------------------------------------- /api/gr_api.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2024 Robin Jarry 3 | 4 | #ifndef _GR_API 5 | #define _GR_API 6 | 7 | #include 8 | #include 9 | 10 | struct gr_api_request { 11 | uint32_t id; 12 | uint32_t type; 13 | uint32_t payload_len; 14 | }; 15 | 16 | struct gr_api_response { 17 | uint32_t for_id; // matches gr_api_request.id 18 | uint32_t status; // uses errno values 19 | uint32_t payload_len; 20 | }; 21 | 22 | #define GR_API_MAX_MSG_LEN (128 * 1024) 23 | 24 | #define REQUEST_TYPE(module, id) (((uint32_t)(0xffff & module) << 16) | (0xffff & id)) 25 | #define EVENT_TYPE(module, id) (((uint32_t)(0xffff & module) << 16) | (0xffff & id)) 26 | #define EVENT_TYPE_ALL UINT32_C(0xffffffff) 27 | 28 | #define GR_DEFAULT_SOCK_PATH "/run/grout.sock" 29 | 30 | struct gr_api_client; 31 | 32 | struct gr_api_client *gr_api_client_connect(const char *sock_path); 33 | 34 | int gr_api_client_disconnect(struct gr_api_client *); 35 | 36 | int gr_api_client_send_recv( 37 | const struct gr_api_client *, 38 | uint32_t req_type, 39 | size_t tx_len, 40 | const void *tx_data, 41 | void **rx_data 42 | ); 43 | 44 | #define GR_MAIN_MODULE 0xcafe 45 | 46 | #define GR_MAIN_HELLO REQUEST_TYPE(GR_MAIN_MODULE, 0x1981) 47 | struct gr_hello_req { 48 | char version[128]; 49 | }; 50 | // struct gr_hello_resp { }; 51 | 52 | #define GR_MAIN_EVENT_SUBSCRIBE REQUEST_TYPE(GR_MAIN_MODULE, 0xcafe) 53 | struct gr_event_subscribe_req { 54 | uint32_t ev_type; 55 | }; 56 | // struct gr_event_subscribe_resp { }; 57 | #define GR_MAIN_EVENT_UNSUBSCRIBE REQUEST_TYPE(GR_MAIN_MODULE, 0xcaff) 58 | // struct gr_event_unsubscribe_req { }; 59 | // struct gr_event_unsubscribe_resp { }; 60 | 61 | struct gr_api_event { 62 | uint32_t ev_type; 63 | size_t payload_len; 64 | }; 65 | 66 | int gr_api_client_event_recv(const struct gr_api_client *, struct gr_api_event **); 67 | 68 | #endif 69 | -------------------------------------------------------------------------------- /api/gr_bitops.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2024 Robin Jarry 3 | 4 | #ifndef _GR_BITOPS 5 | #define _GR_BITOPS 6 | 7 | #include 8 | 9 | #define GR_BIT8(n) (UINT8_C(1) << (n)) 10 | #define GR_BIT16(n) (UINT16_C(1) << (n)) 11 | #define GR_BIT32(n) (UINT32_C(1) << (n)) 12 | #define GR_BIT64(n) (UINT64_C(1) << (n)) 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /api/gr_clock.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2025 Robin Jarry 3 | 4 | #ifndef _GR_CLOCK 5 | #define _GR_CLOCK 6 | 7 | #include 8 | #include 9 | 10 | //! Get the elapsed time since last boot (using a common clock across all processes). 11 | static inline struct timespec gr_clock_raw(void) { 12 | struct timespec tp = {0}; 13 | clock_gettime(CLOCK_MONOTONIC_RAW, &tp); 14 | return tp; 15 | } 16 | 17 | //! Get elapsed time since last boot in microseconds. 18 | static inline clock_t gr_clock_us(void) { 19 | struct timespec tp = gr_clock_raw(); 20 | return (tp.tv_sec * CLOCKS_PER_SEC) + (tp.tv_nsec / 1000); 21 | } 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /api/gr_errno.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2024 Robin Jarry 3 | 4 | #ifndef _GR_API_ERRNO 5 | #define _GR_API_ERRNO 6 | 7 | #include 8 | #include 9 | 10 | static inline int errno_set(int errnum) { 11 | errno = errnum; 12 | return -errnum; 13 | } 14 | 15 | static inline void *errno_set_null(int errnum) { 16 | errno = errnum; 17 | return NULL; 18 | } 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /api/gr_macro.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2024 Robin Jarry 3 | 4 | #ifndef _GR_MACRO 5 | #define _GR_MACRO 6 | 7 | #include 8 | 9 | #define ARRAY_DIM(array) (sizeof(array) / sizeof(array[0])) 10 | #define MEMBER_SIZE(type, member) (sizeof(((type *)0)->member)) 11 | #define PAYLOAD(header) ((void *)(header + 1)) 12 | 13 | // Define a structure as a base for another one using anonymous tagged structure extension. 14 | #define BASE(typename) \ 15 | union { \ 16 | struct typename base; \ 17 | struct typename; \ 18 | } 19 | 20 | // Call a function writing on a buffer called 'buf'. 21 | // 22 | // The offset at which to write is expected to be named 'n'. 23 | // 24 | // The function is expected to return a positive integer holding the number of 25 | // bytes written or a negative value on error. If a negative value is returned, 26 | // the macro will goto an 'err' label. 27 | // 28 | // On success, 'n' is incremented with the number of bytes written. 29 | #define SAFE_BUF(func, buf_size, ...) \ 30 | do { \ 31 | int __s = func(buf + n, buf_size - n, __VA_ARGS__); \ 32 | if (__s < 0) \ 33 | goto err; \ 34 | if (__s >= (int)(buf_size - n)) { \ 35 | errno = ENOBUFS; \ 36 | goto err; \ 37 | } \ 38 | n += __s; \ 39 | } while (0) 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /api/gr_net_compat.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2024 Robin Jarry 3 | 4 | #ifndef _GR_NET_COMPAT 5 | #define _GR_NET_COMPAT 6 | 7 | #include 8 | #include 9 | 10 | #define RTE_IPV6_ADDR_SIZE 16 11 | #define RTE_IPV6_MAX_DEPTH (RTE_IPV6_ADDR_SIZE * CHAR_BIT) 12 | 13 | struct rte_ipv6_addr { 14 | uint8_t a[RTE_IPV6_ADDR_SIZE]; 15 | }; 16 | 17 | static inline void rte_ipv6_addr_mask(struct rte_ipv6_addr *ip, uint8_t depth) { 18 | if (depth < RTE_IPV6_MAX_DEPTH) { 19 | unsigned int d = depth / CHAR_BIT; 20 | uint8_t mask = ~(UINT8_MAX >> (depth % CHAR_BIT)); 21 | ip->a[d] &= mask; 22 | d++; 23 | while (d < sizeof(*ip)) 24 | ip->a[d++] = 0; 25 | } 26 | } 27 | 28 | #define RTE_ETHER_ADDR_LEN 6 29 | 30 | struct __attribute__((aligned(2))) rte_ether_addr { 31 | uint8_t addr_bytes[RTE_ETHER_ADDR_LEN]; 32 | }; 33 | 34 | /* ICMP packet types */ 35 | #define RTE_ICMP_TYPE_ECHO_REPLY 0 36 | #define RTE_ICMP_TYPE_DEST_UNREACHABLE 3 37 | #define RTE_ICMP_TYPE_REDIRECT 5 38 | #define RTE_ICMP_TYPE_ECHO_REQUEST 8 39 | #define RTE_ICMP_TYPE_TTL_EXCEEDED 11 40 | #define RTE_ICMP_TYPE_PARAM_PROBLEM 12 41 | #define RTE_ICMP_TYPE_TIMESTAMP_REQUEST 13 42 | #define RTE_ICMP_TYPE_TIMESTAMP_REPLY 14 43 | 44 | /* Destination Unreachable codes */ 45 | #define RTE_ICMP_CODE_UNREACH_NET 0 46 | #define RTE_ICMP_CODE_UNREACH_HOST 1 47 | #define RTE_ICMP_CODE_UNREACH_PROTO 2 48 | #define RTE_ICMP_CODE_UNREACH_PORT 3 49 | #define RTE_ICMP_CODE_UNREACH_FRAG 4 50 | #define RTE_ICMP_CODE_UNREACH_SRC 5 51 | 52 | /* Time Exceeded codes */ 53 | #define RTE_ICMP_CODE_TTL_EXCEEDED 0 54 | #define RTE_ICMP_CODE_TTL_FRAG 1 55 | 56 | /* Redirect codes */ 57 | #define RTE_ICMP_CODE_REDIRECT_NET 0 58 | #define RTE_ICMP_CODE_REDIRECT_HOST 1 59 | #define RTE_ICMP_CODE_REDIRECT_TOS_NET 2 60 | #define RTE_ICMP_CODE_REDIRECT_TOS_HOST 3 61 | 62 | /* ICMPv6 packet types */ 63 | #define ICMP6_TYPE_ERR_DEST_UNREACH 1 64 | #define ICMP6_TYPE_ERR_PKT_TOO_BIG 2 65 | #define ICMP6_TYPE_ERR_TTL_EXCEEDED 3 66 | #define ICMP6_TYPE_ERR_PARAM_PROBLEM 4 67 | #define ICMP6_TYPE_ECHO_REQUEST 128 68 | #define ICMP6_TYPE_ECHO_REPLY 129 69 | 70 | #endif 71 | -------------------------------------------------------------------------------- /api/gr_net_types.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2024 Robin Jarry 3 | 4 | #ifndef _GR_NET_TYPES 5 | #define _GR_NET_TYPES 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #ifdef __GROUT_MAIN__ 20 | #include 21 | #include 22 | #else 23 | #include 24 | #endif 25 | 26 | // Custom printf specifiers 27 | 28 | // struct rte_ether_addr * 29 | #define ETH_F "%2p" 30 | // ip4_addr_t * 31 | #define IP4_F "%4p" 32 | // struct rte_ipv6_addr * 33 | #define IP6_F "%6p" 34 | // Either ETH_F, IP4 or IP6 depending on the width argument 35 | #define ADDR_F "%*p" 36 | 37 | #define ADDR_W(family) (family == AF_INET ? 4 : (family == AF_INET6 ? 6 : 0)) 38 | 39 | #define ETH_ADDR_RE "^[[:xdigit:]]{2}(:[[:xdigit:]]{2}){5}$" 40 | 41 | #define IPV4_ATOM "(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])" 42 | #define __IPV4_RE IPV4_ATOM "(\\." IPV4_ATOM "){3}" 43 | #define IPV4_RE "^" __IPV4_RE "$" 44 | #define IPV4_NET_RE "^" __IPV4_RE "/(3[0-2]|[12][0-9]|[0-9])$" 45 | 46 | typedef uint32_t ip4_addr_t; 47 | 48 | struct ip4_net { 49 | ip4_addr_t ip; 50 | uint8_t prefixlen; 51 | }; 52 | 53 | static inline bool ip4_addr_same_subnet(ip4_addr_t a, ip4_addr_t b, uint8_t prefixlen) { 54 | ip4_addr_t mask = htonl(~(UINT32_MAX >> prefixlen)); 55 | return ((a ^ b) & mask) == 0; 56 | } 57 | 58 | static inline int ip4_net_parse(const char *s, struct ip4_net *net, bool zero_mask) { 59 | char *addr = NULL; 60 | int ret = -1; 61 | 62 | if (sscanf(s, "%m[0-9.]/%hhu%*c", &addr, &net->prefixlen) != 2) { 63 | errno = EINVAL; 64 | goto out; 65 | } 66 | if (net->prefixlen > 32) { 67 | errno = EINVAL; 68 | goto out; 69 | } 70 | if (inet_pton(AF_INET, addr, &net->ip) != 1) { 71 | errno = EINVAL; 72 | goto out; 73 | } 74 | if (zero_mask) { 75 | // mask non network bits to zero 76 | net->ip &= htonl((uint32_t)(UINT64_MAX << (32 - net->prefixlen))); 77 | } 78 | ret = 0; 79 | out: 80 | free(addr); 81 | return ret; 82 | } 83 | 84 | #define IPV6_ATOM "([A-Fa-f0-9]{1,4})" 85 | #define __IPV6_RE "(" IPV6_ATOM "|::?){2,15}(:" IPV6_ATOM "(\\." IPV4_ATOM "){3})?" 86 | #define IPV6_RE "^" __IPV6_RE "$" 87 | #define IPV6_NET_RE "^" __IPV6_RE "/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$" 88 | 89 | struct ip6_net { 90 | struct rte_ipv6_addr ip; 91 | uint8_t prefixlen; 92 | }; 93 | 94 | static inline int ip6_net_parse(const char *s, struct ip6_net *net, bool zero_mask) { 95 | char *addr = NULL; 96 | int ret = -1; 97 | 98 | if (sscanf(s, "%m[A-Fa-f0-9:.]/%hhu%*c", &addr, &net->prefixlen) != 2) { 99 | errno = EINVAL; 100 | goto out; 101 | } 102 | if (net->prefixlen > RTE_IPV6_MAX_DEPTH) { 103 | errno = EINVAL; 104 | goto out; 105 | } 106 | if (inet_pton(AF_INET6, addr, &net->ip) != 1) { 107 | errno = EINVAL; 108 | goto out; 109 | } 110 | if (zero_mask) { 111 | // mask non network bits to zero 112 | rte_ipv6_addr_mask(&net->ip, net->prefixlen); 113 | } 114 | ret = 0; 115 | out: 116 | free(addr); 117 | return ret; 118 | } 119 | 120 | #endif 121 | -------------------------------------------------------------------------------- /api/gr_string.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2024 Robin Jarry 3 | 4 | #ifndef _GR_STRING 5 | #define _GR_STRING 6 | 7 | #include 8 | #include 9 | 10 | char *astrcat(char *buf, const char *fmt, ...) __attribute__((format(printf, 2, 3))); 11 | char *strjoin(char **array, size_t len, const char *sep); 12 | int charset_check(const char *buf, size_t maxlen); 13 | 14 | // Return human readable representation of a cpuset. The output format is 15 | // a list of CPUs with ranges (for example, "0,1,3-9"). 16 | int cpuset_format(char *buf, size_t len, const cpu_set_t *set); 17 | 18 | // Parse a list of CPUs (e.g. "0,1,3-9") to a cpu_set_t object. 19 | int cpuset_parse(cpu_set_t *set, const char *buf); 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /api/meson.build: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Copyright (c) 2024 Robin Jarry 3 | 4 | api_headers += files( 5 | 'gr_api.h', 6 | 'gr_api_client_impl.h', 7 | 'gr_bitops.h', 8 | 'gr_clock.h', 9 | 'gr_errno.h', 10 | 'gr_macro.h', 11 | 'gr_net_compat.h', 12 | 'gr_net_types.h', 13 | ) 14 | api_headers += configure_file( 15 | output: 'gr_version.h', 16 | configuration: configuration_data({ 17 | 'GROUT_VERSION': '"' + meson.project_version() + '"', 18 | }), 19 | ) 20 | 21 | api_src = files( 22 | 'printf.c', 23 | 'string.c', 24 | ) 25 | src += api_src 26 | cli_src += api_src 27 | 28 | api_inc += include_directories('.') 29 | 30 | tests += [ 31 | { 32 | 'sources': files('printf_test.c', 'printf.c'), 33 | 'link_args': [], 34 | }, 35 | { 36 | 'sources': files('string_test.c', 'string.c'), 37 | 'link_args': [], 38 | }, 39 | ] 40 | -------------------------------------------------------------------------------- /api/printf.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2024 Robin Jarry 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | static int format_pointer(FILE *f, const struct printf_info *info, const void *const *args) { 12 | char buf[INET6_ADDRSTRLEN]; 13 | const void *arg = *(const void **)*args; 14 | 15 | if (arg == NULL) 16 | return fprintf(f, "(nil)"); 17 | 18 | switch (info->width) { 19 | case 2: // struct rte_ether_addr * 20 | const struct rte_ether_addr *mac = arg; 21 | return fprintf( 22 | f, 23 | "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx", 24 | mac->addr_bytes[0], 25 | mac->addr_bytes[1], 26 | mac->addr_bytes[2], 27 | mac->addr_bytes[3], 28 | mac->addr_bytes[4], 29 | mac->addr_bytes[5] 30 | ); 31 | case 4: // ip4_addr_t * 32 | inet_ntop(AF_INET, arg, buf, sizeof(buf)); 33 | return fprintf(f, "%s", buf); 34 | case 6: // struct rte_ipv6_addr * 35 | inet_ntop(AF_INET6, arg, buf, sizeof(buf)); 36 | return fprintf(f, "%s", buf); 37 | } 38 | 39 | return fprintf(f, "0x%lx", (uintptr_t)arg); 40 | } 41 | 42 | static int check_pointer_arg(const struct printf_info *, size_t n, int *argtypes, int *sizes) { 43 | if (n == 1) { 44 | sizes[0] = sizeof(void *); 45 | argtypes[0] = PA_POINTER; 46 | return 1; 47 | } 48 | return -1; 49 | } 50 | 51 | static void __attribute__((constructor, used)) init(void) { 52 | if (register_printf_specifier('p', format_pointer, check_pointer_arg) < 0) 53 | abort(); 54 | } 55 | -------------------------------------------------------------------------------- /api/printf_test.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2024 Robin Jarry 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | static void ether(void **) { 11 | struct rte_ether_addr mac = {{0x90, 0x2e, 0x16, 0x55, 0x8e, 0x6a}}; 12 | char buf[64]; 13 | buf[0] = 0; 14 | snprintf(buf, sizeof(buf), ETH_F, &mac); 15 | assert_string_equal(buf, "90:2e:16:55:8e:6a"); 16 | buf[0] = 0; 17 | snprintf(buf, sizeof(buf), ETH_F, NULL); 18 | assert_string_equal(buf, "(nil)"); 19 | buf[0] = 0; 20 | snprintf(buf, sizeof(buf), ADDR_F, 2, &mac); 21 | assert_string_equal(buf, "90:2e:16:55:8e:6a"); 22 | buf[0] = 0; 23 | snprintf(buf, sizeof(buf), ADDR_F, 2, NULL); 24 | assert_string_equal(buf, "(nil)"); 25 | } 26 | 27 | static void ipv4(void **) { 28 | ip4_addr_t ip4 = htonl(0x01020304); 29 | char buf[64]; 30 | buf[0] = 0; 31 | snprintf(buf, sizeof(buf), IP4_F, &ip4); 32 | assert_string_equal(buf, "1.2.3.4"); 33 | buf[0] = 0; 34 | snprintf(buf, sizeof(buf), IP4_F, NULL); 35 | assert_string_equal(buf, "(nil)"); 36 | buf[0] = 0; 37 | snprintf(buf, sizeof(buf), ADDR_F, ADDR_W(AF_INET), &ip4); 38 | assert_string_equal(buf, "1.2.3.4"); 39 | buf[0] = 0; 40 | snprintf(buf, sizeof(buf), ADDR_F, ADDR_W(AF_INET), NULL); 41 | assert_string_equal(buf, "(nil)"); 42 | } 43 | 44 | static void ipv6(void **) { 45 | struct rte_ipv6_addr ip6 = { 46 | {0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0x2e, 0x8b, 0x35, 0x2e, 0x66, 0xbf, 0x3a, 0xd8} 47 | }; 48 | char buf[64]; 49 | buf[0] = 0; 50 | snprintf(buf, sizeof(buf), IP6_F, &ip6); 51 | assert_string_equal(buf, "fe80::2e8b:352e:66bf:3ad8"); 52 | buf[0] = 0; 53 | snprintf(buf, sizeof(buf), IP6_F, NULL); 54 | assert_string_equal(buf, "(nil)"); 55 | buf[0] = 0; 56 | snprintf(buf, sizeof(buf), ADDR_F, ADDR_W(AF_INET6), &ip6); 57 | assert_string_equal(buf, "fe80::2e8b:352e:66bf:3ad8"); 58 | buf[0] = 0; 59 | snprintf(buf, sizeof(buf), ADDR_F, ADDR_W(AF_INET6), NULL); 60 | assert_string_equal(buf, "(nil)"); 61 | } 62 | 63 | static void pointers(void **) { 64 | void *p = (void *)(uintptr_t)0x7ffd3aae340c; 65 | char buf[64]; 66 | buf[0] = 0; 67 | snprintf(buf, sizeof(buf), "%p", p); 68 | assert_string_equal(buf, "0x7ffd3aae340c"); 69 | buf[0] = 0; 70 | snprintf(buf, sizeof(buf), "%1337p", p); 71 | assert_string_equal(buf, "0x7ffd3aae340c"); 72 | buf[0] = 0; 73 | snprintf(buf, sizeof(buf), "%42p", p); 74 | assert_string_equal(buf, "0x7ffd3aae340c"); 75 | buf[0] = 0; 76 | snprintf(buf, sizeof(buf), "%p", NULL); 77 | assert_string_equal(buf, "(nil)"); 78 | buf[0] = 0; 79 | snprintf(buf, sizeof(buf), "%1337p", NULL); 80 | assert_string_equal(buf, "(nil)"); 81 | buf[0] = 0; 82 | snprintf(buf, sizeof(buf), "%42p", NULL); 83 | assert_string_equal(buf, "(nil)"); 84 | } 85 | 86 | int main(void) { 87 | const struct CMUnitTest tests[] = { 88 | cmocka_unit_test(ether), 89 | cmocka_unit_test(ipv4), 90 | cmocka_unit_test(ipv6), 91 | cmocka_unit_test(pointers), 92 | }; 93 | return cmocka_run_group_tests(tests, NULL, NULL); 94 | } 95 | -------------------------------------------------------------------------------- /cli/complete.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2023 Robin Jarry 3 | 4 | #ifndef _GR_CLI_COMPLETE 5 | #define _GR_CLI_COMPLETE 6 | 7 | #include 8 | 9 | int bash_complete(struct ec_node *cmdlist); 10 | 11 | #endif 12 | -------------------------------------------------------------------------------- /cli/exec.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2023 Robin Jarry 3 | 4 | #ifndef _GR_CLI_EXEC 5 | #define _GR_CLI_EXEC 6 | 7 | #include 8 | 9 | #include 10 | 11 | struct ec_node *init_commands(void); 12 | 13 | typedef enum { 14 | EXEC_SUCCESS, 15 | EXEC_LEX_ERROR, // unterminated quote/escape, only for exec_line 16 | EXEC_CMD_EMPTY, // no arguments after lexing 17 | EXEC_CMD_EXIT, // callback asked to exit 18 | EXEC_CMD_INVALID_ARGS, // command not recognized 19 | EXEC_CMD_FAILED, // command callback returned an error 20 | EXEC_CB_UNDEFINED, // no callback registered, internal error 21 | EXEC_OTHER_ERROR, // other internal error 22 | } exec_status_t; 23 | 24 | #define CALLBACK_ATTR "callback" 25 | 26 | exec_status_t exec_line(const struct gr_api_client *, const struct ec_node *, const char *line); 27 | 28 | exec_status_t exec_args( 29 | const struct gr_api_client *, 30 | const struct ec_node *, 31 | size_t argc, 32 | const char *const *argv 33 | ); 34 | 35 | #endif 36 | -------------------------------------------------------------------------------- /cli/gr_table.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2024 Robin Jarry 3 | 4 | #ifndef _GR_TABLE 5 | #define _GR_TABLE 6 | 7 | #ifndef NEED_SCOLS_LINE_SPRINTF 8 | #include 9 | #else 10 | struct libscols_line; 11 | 12 | int scols_line_sprintf(struct libscols_line *, int column, const char *fmt, ...) 13 | __attribute__((format(printf, 3, 4))); 14 | #endif 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /cli/grcli.bash-completion: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # SPDX-License-Identifier: BSD-3-Clause 3 | # Copyright (c) 2024 Robin Jarry 4 | 5 | complete -o default -C 'grcli --bash-complete' grcli 6 | -------------------------------------------------------------------------------- /cli/interact.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2023 Robin Jarry 3 | 4 | #ifndef _GR_CLI_INTERACT 5 | #define _GR_CLI_INTERACT 6 | 7 | #include 8 | 9 | #include 10 | 11 | int interact(const struct gr_api_client *, struct ec_node *); 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /cli/log.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2023 Robin Jarry 3 | 4 | #include "log.h" 5 | 6 | #include 7 | 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | static bool stdin_isatty; 14 | static bool stdout_isatty; 15 | static bool stderr_isatty; 16 | 17 | void tty_init(void) { 18 | stdin_isatty = isatty(0); 19 | stdout_isatty = isatty(1); 20 | stderr_isatty = isatty(2); 21 | } 22 | 23 | bool is_tty(const FILE *f) { 24 | if (f == stdin) 25 | return stdin_isatty; 26 | if (f == stdout) 27 | return stdout_isatty; 28 | if (f == stderr) 29 | return stderr_isatty; 30 | return false; 31 | } 32 | 33 | void errorf(const char *fmt, ...) { 34 | const char *color, *reset; 35 | va_list ap; 36 | 37 | if (stderr_isatty) { 38 | color = BOLD_RED_SGR; 39 | reset = RESET_SGR; 40 | } else { 41 | color = ""; 42 | reset = ""; 43 | } 44 | fprintf(stderr, "%serror:%s ", color, reset); 45 | va_start(ap, fmt); 46 | vfprintf(stderr, fmt, ap); 47 | va_end(ap); 48 | fprintf(stderr, "\n"); 49 | } 50 | 51 | int print_cmd_status(exec_status_t status) { 52 | switch (status) { 53 | case EXEC_SUCCESS: 54 | case EXEC_CMD_EMPTY: 55 | case EXEC_CMD_EXIT: 56 | return 0; 57 | case EXEC_LEX_ERROR: 58 | errorf("unterminated quote/escape"); 59 | break; 60 | case EXEC_CMD_INVALID_ARGS: 61 | errorf("invalid arguments"); 62 | break; 63 | case EXEC_CMD_FAILED: 64 | errorf("command failed: %s", strerror(errno)); 65 | break; 66 | case EXEC_CB_UNDEFINED: 67 | errorf("no callback defined for command"); 68 | break; 69 | case EXEC_OTHER_ERROR: 70 | errorf("fatal: %s", strerror(errno)); 71 | break; 72 | } 73 | return -errno; 74 | } 75 | 76 | static const char *need_quote(const char *arg) { 77 | while (*arg != '\0') { 78 | switch (*arg++) { 79 | case ' ': 80 | case '\t': 81 | case '"': 82 | case '\\': 83 | return "'"; 84 | case '\'': 85 | return "\""; 86 | } 87 | } 88 | return ""; 89 | } 90 | 91 | void trace_cmd(const char *line) { 92 | struct ec_strvec *vec; 93 | size_t len; 94 | 95 | if ((vec = ec_strvec_sh_lex_str(line, 0, NULL)) == NULL) 96 | goto end; 97 | 98 | len = ec_strvec_len(vec); 99 | if (len == 0) 100 | goto end; 101 | 102 | fprintf(stderr, "%s+", stderr_isatty ? CYAN_SGR : ""); 103 | 104 | for (size_t i = 0; i < len; i++) { 105 | const char *arg = ec_strvec_val(vec, i); 106 | const char *quote = need_quote(arg); 107 | fprintf(stderr, " %s%s%s", quote, arg, quote); 108 | } 109 | 110 | fprintf(stderr, "%s\n", stderr_isatty ? RESET_SGR : ""); 111 | 112 | end: 113 | ec_strvec_free(vec); 114 | } 115 | -------------------------------------------------------------------------------- /cli/log.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2023 Robin Jarry 3 | 4 | #ifndef _GR_CLI_LOG 5 | #define _GR_CLI_LOG 6 | 7 | #include "exec.h" 8 | 9 | #include 10 | 11 | #define BOLD_RED_SGR "\x1b[1;31m" 12 | #define BOLD_YELLOW_SGR "\x1b[1;33m" 13 | #define CYAN_SGR "\x1b[36m" 14 | #define RESET_SGR "\x1b[0m" 15 | 16 | void tty_init(void); 17 | 18 | bool is_tty(const FILE *); 19 | 20 | int print_cmd_status(exec_status_t status); 21 | 22 | void trace_cmd(const char *line); 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /cli/meson.build: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Copyright (c) 2023 Robin Jarry 3 | 4 | cli_src += files( 5 | 'complete.c', 6 | 'ec_node_devargs.c', 7 | 'ec_node_dyn.c', 8 | 'ecoli.c', 9 | 'exec.c', 10 | 'interact.c', 11 | 'log.c', 12 | 'main.c', 13 | 'quit.c', 14 | ) 15 | 16 | if not compiler.has_function( 17 | 'scols_line_sprintf', 18 | prefix: '#include ', 19 | dependencies: [smartcols_dep], 20 | ) 21 | cli_src += files('table.c') 22 | cli_cflags += ['-DNEED_SCOLS_LINE_SPRINTF'] 23 | endif 24 | 25 | cli_inc += include_directories('.') 26 | -------------------------------------------------------------------------------- /cli/quit.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2023 Robin Jarry 3 | 4 | #include 5 | #include 6 | 7 | static cmd_status_t quit(const struct gr_api_client *, const struct ec_pnode *) { 8 | return CMD_EXIT; 9 | } 10 | 11 | static int ctx_init(struct ec_node *root) { 12 | return CLI_COMMAND(root, "quit", quit, "Exit the CLI."); 13 | } 14 | 15 | static struct gr_cli_context ctx = { 16 | .name = "quit", 17 | .init = ctx_init, 18 | }; 19 | 20 | static void __attribute__((constructor, used)) init(void) { 21 | register_context(&ctx); 22 | } 23 | -------------------------------------------------------------------------------- /cli/table.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2024 Robin Jarry 3 | 4 | #include 5 | 6 | #include 7 | 8 | #include 9 | 10 | int scols_line_sprintf(struct libscols_line *line, int column, const char *fmt, ...) { 11 | char buf[256]; 12 | va_list ap; 13 | int ret; 14 | 15 | va_start(ap, fmt); 16 | ret = vsnprintf(buf, sizeof(buf), fmt, ap); 17 | va_end(ap); 18 | 19 | if (ret < 0) 20 | return ret; 21 | 22 | return scols_line_set_data(line, column, buf); 23 | } 24 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: grout 2 | Section: net 3 | Priority: optional 4 | Maintainer: Robin Jarry 5 | Uploaders: 6 | Robin Jarry , 7 | Build-Depends: 8 | debhelper-compat (= 13), 9 | bash-completion, 10 | git, 11 | go-md2man, 12 | libarchive-dev, 13 | libcmocka-dev, 14 | libedit-dev, 15 | libevent-dev, 16 | libibverbs-dev, 17 | libnuma-dev, 18 | libsmartcols-dev, 19 | meson, 20 | ninja-build, 21 | patch, 22 | pkg-config, 23 | python3-pyelftools, 24 | Standards-Version: 4.7.0 25 | Rules-Requires-Root: no 26 | Homepage: https://github.com/DPDK/grout 27 | Vcs-Browser: https://github.com/DPDK/grout 28 | Vcs-Git: https://github.com/DPDK/grout 29 | 30 | Package: grout 31 | Architecture: linux-any 32 | Depends: 33 | driverctl, 34 | ${misc:Depends}, 35 | ${shlibs:Depends}, 36 | Description: Graph router based on DPDK 37 | grout stands for Graph Router. In English, "grout" refers to thin mortar that 38 | hardens to fill gaps between tiles. 39 | . 40 | grout is a DPDK based network processing application. It uses the rte_graph 41 | library for data path processing. 42 | . 43 | Its main purpose is to simulate a network function or a physical router for 44 | testing/replicating real (usually closed source) VNF/CNF behavior with an 45 | opensource tool. 46 | . 47 | It comes with a client library to configure it over a standard UNIX socket and 48 | a CLI that uses that library. The CLI can be used as an interactive shell, but 49 | also in scripts one command at a time, or by batches. 50 | 51 | Package: grout-dev 52 | Architecture: all 53 | Depends: 54 | ${misc:Depends}, 55 | Suggests: 56 | grout, 57 | Description: API headers for grout clients 58 | grout stands for Graph Router. In English, "grout" refers to thin mortar that 59 | hardens to fill gaps between tiles. 60 | . 61 | grout is a DPDK based network processing application. It uses the rte_graph 62 | library for data path processing. 63 | . 64 | Its main purpose is to simulate a network function or a physical router for 65 | testing/replicating real (usually closed source) VNF/CNF behavior with an 66 | opensource tool. 67 | . 68 | It comes with a client library to configure it over a standard UNIX socket and 69 | a CLI that uses that library. The CLI can be used as an interactive shell, but 70 | also in scripts one command at a time, or by batches. 71 | -------------------------------------------------------------------------------- /debian/grout-dev.install: -------------------------------------------------------------------------------- 1 | /usr/include/*.h 2 | -------------------------------------------------------------------------------- /debian/grout.bash-completion: -------------------------------------------------------------------------------- 1 | main/grout.bash-completion grcli 2 | cli/grcli.bash-completion grout 3 | -------------------------------------------------------------------------------- /debian/grout.conffiles: -------------------------------------------------------------------------------- 1 | /etc/default/grout 2 | /etc/grout.init 3 | -------------------------------------------------------------------------------- /debian/grout.install: -------------------------------------------------------------------------------- 1 | /etc/default/grout 2 | /etc/grout.init 3 | /usr/bin/grcli 4 | /usr/bin/grout 5 | /usr/share/man/man1/grcli.1 6 | /usr/share/man/man8/grout.8 7 | -------------------------------------------------------------------------------- /debian/grout.service: -------------------------------------------------------------------------------- 1 | ../main/grout.service -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | # Hardening 4 | export DEB_BUILD_MAINT_OPTIONS = hardening=+all reproducible=+all optimize=-lto 5 | DPKG_EXPORT_BUILDFLAGS = 1 6 | include /usr/share/dpkg/buildflags.mk 7 | 8 | meson_opts := --wrap-mode=default 9 | meson_opts += --auto-features=enabled 10 | meson_opts += -Ddpdk:platform=generic 11 | 12 | %: 13 | dh $@ --buildsystem=meson --with=bash-completion -B$(CURDIR)/debian/_build 14 | 15 | override_dh_auto_configure: 16 | dh_auto_configure -- $(meson_opts) 17 | 18 | override_dh_auto_install: 19 | meson install -C debian/_build --skip-subprojects --destdir=$(CURDIR)/debian/tmp 20 | install -D -m 644 main/grout.default debian/tmp/etc/default/grout 21 | install -D -m 644 main/grout.init debian/tmp/etc/grout.init 22 | 23 | override_dh_installsystemd: 24 | dh_installsystemd --no-start --no-stop-on-upgrade 25 | -------------------------------------------------------------------------------- /devtools/check-whitespace: -------------------------------------------------------------------------------- 1 | #!/usr/bin/awk -f 2 | # SPDX-License-Identifier: BSD-3-Clause 3 | # Copyright (c) 2024 Robin Jarry 4 | 5 | BEGIN { 6 | isatty = system("test -t 1") == "0" 7 | retcode = 0 8 | } 9 | 10 | function color(code, s) { 11 | if (isatty) { 12 | return "\033[" code "m" s "\033[0m" 13 | } 14 | return s 15 | } 16 | function red(s) { return color("31", s) } 17 | function green(s) { return color("32", s) } 18 | function magenta(s) { return color("35", s) } 19 | function cyan(s) { return color("36", s) } 20 | function bg_red(s) { return color("41", s) } 21 | function hl_ws(s, pattern) { 22 | gsub(pattern, bg_red("&"), s) 23 | # convert tab characters to 8 spaces to allow coloring 24 | gsub(/\t/, " ", s) 25 | return s 26 | } 27 | 28 | / +\t+/ { 29 | retcode = 1 30 | print magenta(FILENAME) cyan(":") green(FNR) cyan(":") \ 31 | hl_ws($0, " +\\t+") red("<-- space(s) followed by tab(s)") 32 | } 33 | 34 | /[ \t]+$/ { 35 | retcode = 1 36 | print magenta(FILENAME) cyan(":") green(FNR) cyan(":") \ 37 | hl_ws($0, "[ \\t]+$") red("<-- trailing whitespace") 38 | } 39 | 40 | ENDFILE { 41 | # will only match on GNU awk, ignored on non-GNU versions 42 | if ($0 ~ /^[ \t]*$/) { 43 | retcode = 1 44 | print magenta(FILENAME) cyan(": ") red("trailing new line(s)") 45 | } else if (RT != "\n") { 46 | retcode = 1 47 | print magenta(FILENAME) cyan(": ") red("no new line at end of file") 48 | } 49 | } 50 | 51 | END { 52 | exit retcode 53 | } 54 | -------------------------------------------------------------------------------- /devtools/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # SPDX-License-Identifier: BSD-3-Clause 3 | # Copyright (c) 2024 Robin Jarry 4 | 5 | set -e 6 | 7 | debug() { 8 | if [ "$GIT_TRAILER_DEBUG" = 1 ]; then 9 | "$@" >&2 10 | fi 11 | } 12 | 13 | trailer_order=" 14 | Fixes: 15 | Closes: 16 | Link: 17 | Cc: 18 | Suggested-by: 19 | Requested-by: 20 | Reported-by: 21 | Signed-off-by: 22 | Co-authored-by: 23 | Tested-by: 24 | Reviewed-by: 25 | Acked-by: 26 | " 27 | file=${1?file} 28 | tmp=$(mktemp) 29 | trap "rm -f $tmp" EXIT 30 | 31 | # Read unfolded trailers and normalize case. 32 | git interpret-trailers --parse --trim-empty "$file" | 33 | while read -r key value; do 34 | # Force title case on trailer key. 35 | first_letter=$(echo "$key" | sed 's/^\(.\).*/\1/' | tr '[:lower:]' '[:upper:]') 36 | other_letters=$(echo "$key" | sed 's/^.\(.*\)/\1/' | tr '[:upper:]' '[:lower:]') 37 | key="$first_letter$other_letters" 38 | 39 | # Find sort order of this key. 40 | order=$(echo "$trailer_order" | grep -Fxn "$key" | sed -nE 's/^([0-9]+):.*/\1/p') 41 | if [ -z "$order" ]; then 42 | echo "warning: unknown trailer '$key'" >&2 43 | # Unknown trailers are always first. 44 | order="0" 45 | fi 46 | 47 | echo "$order $key $value" 48 | done | 49 | # Sort trailers according to their numeric order, trim the numeric order. 50 | LC_ALL=C sort -n | sed -E 's/^[0-9]+ //' > "$tmp" 51 | 52 | debug echo ==== sanitized trailers ==== 53 | debug cat "$tmp" 54 | 55 | # Unfortunately, reordering trailers is not possible at the moment. Delete all 56 | # trailers first. The only way to do it is to force replace existing trailers 57 | # with empty values and trim empty trailers one by one. 58 | while read -r key value; do 59 | git interpret-trailers --in-place --if-exists=replace \ 60 | --trailer="$key " "$file" 61 | git interpret-trailers --in-place --trim-empty "$file" 62 | done < "$tmp" 63 | 64 | set -- 65 | while read -r trailer; do 66 | set -- "$@" --trailer="$trailer" 67 | done < "$tmp" 68 | 69 | # Remove duplicate "key: value" trailers (e.g. duplicate signed-off-by). 70 | git interpret-trailers --in-place --if-exists=addIfDifferent "$@" "$file" 71 | -------------------------------------------------------------------------------- /devtools/cross/aarch64.ini: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Copyright (c) 2025 Robin Jarry 3 | 4 | [binaries] 5 | c = ['ccache', 'aarch64-linux-gnu-gcc'] 6 | cpp = ['ccache', 'aarch64-linux-gnu-g++'] 7 | ar = 'aarch64-linux-gnu-ar' 8 | strip = 'aarch64-linux-gnu-strip' 9 | pkg-config = '/usr/bin/pkg-config' 10 | 11 | [host_machine] 12 | system = 'linux' 13 | cpu_family = 'aarch64' 14 | cpu = 'armv8-a' 15 | endian = 'little' 16 | 17 | [properties] 18 | platform = 'generic' 19 | pkg_config_libdir = '/usr/lib/aarch64-linux-gnu/pkgconfig' 20 | 21 | [dpdk:project options] 22 | platform = 'generic' 23 | enable_drivers = 'net/null' 24 | -------------------------------------------------------------------------------- /devtools/git-stats: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # SPDX-License-Identifier: BSD-3-Clause 3 | # Copyright (c) 2024 Robin Jarry 4 | 5 | set -e 6 | set -o pipefail 7 | 8 | columns="Author,Commits,Changed Files,Insertions,Deletions" 9 | 10 | git shortlog -sn "$@" | 11 | while read -r commits author; do 12 | git log --author="$author" --pretty=tformat: --numstat "$@" | { 13 | adds=0 14 | subs=0 15 | files=0 16 | while read -r a s f; do 17 | if [ "$a" = "-" ]; then 18 | a=1 19 | fi 20 | if [ "$s" = "-" ]; then 21 | s=1 22 | fi 23 | adds=$((adds + a)) 24 | subs=$((subs + s)) 25 | files=$((files + 1)) 26 | done 27 | printf '%s;%d;%d;%+d;%+d;\n' \ 28 | "$author" "$commits" "$files" "$adds" "-$subs" 29 | } 30 | done | 31 | column -t -s ';' -N "$columns" -R "${columns#*,}" | 32 | sed -E 's/[[:space:]]+$//' 33 | 34 | echo 35 | 36 | columns="Reviewer/Tester,Commits" 37 | 38 | git shortlog -sn \ 39 | --group=trailer:acked-by \ 40 | --group=trailer:tested-by \ 41 | --group=trailer:reviewed-by "$@" | 42 | while read -r commits author; do 43 | printf '%s;%s\n' "$author" "$commits" 44 | done | 45 | column -t -s ';' -N "$columns" -R "${columns#*,}" | 46 | sed -E 's/[[:space:]]+$//' 47 | -------------------------------------------------------------------------------- /devtools/gpg-signing-key.asc: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP PUBLIC KEY BLOCK----- 2 | 3 | mDMEYaOAcBYJKwYBBAHaRw8BAQdAv4gyJJyJ6Pa352i9dkChWv9InSp3Lcb0hliK 4 | oI3AMKC0HFJvYmluIEphcnJ5IDxyb2JpbkBqYXJyeS5jYz6IkAQTFggAOBYhBNwH 5 | GOMi4sdgXr3IMUaVfsCP0P6QBQJho4BwAhsDBQsJCAcCBhUKCQgLAgQWAgMBAh4B 6 | AheAAAoJEEaVfsCP0P6Qj8oA/iH+GbIH6Eab3cqsASKA/3CQb93XmxqsI9hjsuqn 7 | ba1gAP91eGGfZoNlmc1+vgNgV/20euYhia0phEE9KnEergcLA4kCMwQQAQoAHRYh 8 | BMY9u+8hQnziSdvZawYSEpRGR6QRBQJjCLQTAAoJEAYSEpRGR6QRK/AP/ApUhbIh 9 | dm+ExNnb+kPYvazq3O6nesPWcyOTXGoB/WUunGEom3zPfYcs/+7vmqjMvx+gdBIv 10 | 1zo8wvc5Q0eEAGt2dzCTHsycprCd9eyLWUwujfUzw9hzuE/9kyZY5lQkfVpwm0j6 11 | Ju6t/nRCNhrAB3H+EtHvu9QWCvucuNbbza00tMQKJw1cGVR40tA6IIdoHcmNgG9j 12 | NoY7trts0dRJRvBCIG5uGpL8WC9aizxM+9hKOlpvdBS/+Ys19wRKw634iKyr+3R5 13 | LdqlU73HWSPS/0SLZmomffwRJnHDbIKrUtBRrEqpP/or+9pWndqBEjgJbUXZd9Eg 14 | lNXoI7M3V2ZJFF0Xesd0u1BDrw9fQYs+uLBhOIrbf4gRb5qD/VZvuw+/M9HJleF+ 15 | 9d2Vj8GRYgIWEBSYIyHKlRI81Rzf6pCbbkWV+pBOg7a+yMZuporAKp7u3mTzJkwX 16 | 1m/AXNRjZ6x7MybuGpqvYzbiydgJerYS7M0Iy7jxX9GRO0ScLasUx/MZGYq9JrfN 17 | hG31a5eu3uFjdbyjEIJ/mZLYA99nFT40k//0ubcajP1zg/BiOMGaoSfG0rPjBYCc 18 | 56gAPL+SzVXqyQQis/mjpfjIxV4xYWuEtjizQzzSvU85T6GPTPxFnJPhfcud9xTY 19 | h7+yIc41jgSBQ0RTFGQ8Vnaw1antI5xfmvKUuDgEYaOAcBIKKwYBBAGXVQEFAQEH 20 | QJKMaL7qmDBd6lb+6KCPmfO8hIYWxct8kdo8iceWftIZAwEIB4h+BBgWCAAmAhsM 21 | FiEE3AcY4yLix2BevcgxRpV+wI/Q/pAFAmGjgbMFCQPCaEMACgkQRpV+wI/Q/pDD 22 | owEA+3ZS2AzhG4Hk80oOUCcn7hv0NPHqaYa6vX2c/8l1QxUA/jkCAUuKGZSpvSbf 23 | jmHlsElCPYOJIx+Ov92vyjP/eIoCiH4EGBYIACYCGwwWIQTcBxjjIuLHYF69yDFG 24 | lX7Aj9D+kAUCZYNXnQUJB6I+LQAKCRBGlX7Aj9D+kADcAP4xTYnCaZVh+31h01+m 25 | Hgc0rPZ/1Uje6yOcr29cLCDF1wEA31jMuITIcqP9hsKuPxTQ2H9NGrSfEGMnF93u 26 | zVU3jgu4MwRho4FCFgkrBgEEAdpHDwEBB0BBwrifCsMD3W97/+Q9JM2VthuZ7tOA 27 | OiIhluxCpE0r0Ih+BBgWCAAmFiEE3AcY4yLix2BevcgxRpV+wI/Q/pAFAmGjgUIC 28 | GyAFCQPCZwAACgkQRpV+wI/Q/pAnhgEA4h0xjyK5dH/G+4OP1xdW2az8E9/Vnm1B 29 | E2A5LQ6lz7EA/RX9XnK5hZgaDEStRnmthCZ2MpIbc430ox+SIBlLoFUOiH4EGBYI 30 | ACYCGyAWIQTcBxjjIuLHYF69yDFGlX7Aj9D+kAUCZYNXtgUJB6I9dAAKCRBGlX7A 31 | j9D+kBWCAQCoqcMewjVTYiX39cQVfL7LnNYfncyytRjEeZIuZIz3FAD9GePQJtbo 32 | ZXNJuUp3Q6oAyqlvDcnxpzv47kH8Sicy/w+4MwRho4FcFgkrBgEEAdpHDwEBB0AY 33 | FtKTC0QyGYBFChL78bax1FZ7YlKw52BgWEBCzAcalYj1BBgWCAAmFiEE3AcY4yLi 34 | x2BevcgxRpV+wI/Q/pAFAmGjgVwCGwIFCQPCZwAAgQkQRpV+wI/Q/pB2IAQZFggA 35 | HRYhBNi2qZB8OrcgQotgaGJxjg1mb8M1BQJho4FcAAoJEGJxjg1mb8M1JNwBAIkO 36 | dCpYHyeU+Y/4WM0vu2Z+d50ShvcTjiBq9i1cIiF9AP938RY0dgvilD7rEAvWOn6t 37 | BKif/vLFrv9OVMLqntNODTXSAP9oMeclstLRWPzBCYKGU7OGg9jTpdyFQVh0Qj0+ 38 | 1YAUbwD8Cm/L+fGHdblS0LYGWqJ3/LbmuT770uQlAL+6oON0SgKI9QQYFggAJgIb 39 | AhYhBNwHGOMi4sdgXr3IMUaVfsCP0P6QBQJlg1fABQkHoj1kAIF2IAQZFggAHRYh 40 | BNi2qZB8OrcgQotgaGJxjg1mb8M1BQJho4FcAAoJEGJxjg1mb8M1JNwBAIkOdCpY 41 | HyeU+Y/4WM0vu2Z+d50ShvcTjiBq9i1cIiF9AP938RY0dgvilD7rEAvWOn6tBKif 42 | /vLFrv9OVMLqntNODQkQRpV+wI/Q/pCUHwD9GMPJuy1lRhP/sb5unjv1KP3tC3Xc 43 | hSlZsx6NK9KW+g4BAKvnon7V0E6MNLCWZQVh/obExwtI6MRQ89KSJuYfzrwD 44 | =BaqK 45 | -----END PGP PUBLIC KEY BLOCK----- 46 | -------------------------------------------------------------------------------- /docs/grcli.1.md: -------------------------------------------------------------------------------- 1 | GRCLI 1 @DATE@ "grout @VERSION@" 2 | ================================ 3 | 4 | # NAME 5 | 6 | **grcli** -- grout command line interface 7 | 8 | # DESCRIPTION 9 | 10 | Grout is a software router based on DPDK __rte_graph__. 11 | 12 | # SYNOPSIS 13 | 14 | **grcli** 15 | [**-e**] 16 | [**-f** _PATH_] 17 | [**-h**] 18 | [**-s** _PATH_] 19 | [**-V**] 20 | [**-x**] 21 | ... 22 | 23 | # OPTIONS 24 | 25 | #### **-e**, **--err-exit** 26 | 27 | Abort on first error. 28 | 29 | #### **-f** _PATH_, **--file** _PATH_ 30 | 31 | Read commands from _PATH_ instead of standard input. 32 | 33 | #### **-h**, **--help** 34 | 35 | Show this help message and exit. 36 | 37 | #### **-s** _PATH_, **--socket** _PATH_ 38 | 39 | Path to the control plane API socket. 40 | 41 | Default: **GROUT_SOCK_PATH** from environment or _/run/grout.sock_. 42 | 43 | #### **-V**, **--version** 44 | 45 | Print version and exit. 46 | 47 | #### **-x**, **--trace-commands** 48 | 49 | Print executed commands. 50 | 51 | # ENVIRONMENT 52 | 53 | #### **DPRC** 54 | 55 | Set the DPRC - Datapath Resource Container: This value should match the 56 | one used by DPDK during the scan of the fslmc bus. It is recommended to 57 | set this on any NXP QorIQ targets. This serves as the entry point for 58 | grcli to enable autocompletion of fslmc devices manageable by grout. 59 | While grcli can configure grout without this environment setting, 60 | autocompletion of the devargs will not be available. 61 | 62 | # SEE ALSO 63 | 64 | **grout**(8) 65 | 66 | # AUTHORS 67 | 68 | Created and maintained by Robin Jarry. 69 | -------------------------------------------------------------------------------- /docs/meson.build: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Copyright (c) 2024 Robin Jarry 3 | 4 | md2man = find_program('go-md2man', native: true, required: get_option('docs')) 5 | if not md2man.found() 6 | subdir_done() 7 | endif 8 | 9 | mandir = get_option('mandir') 10 | config = configuration_data({ 11 | 'VERSION': meson.project_version(), 12 | 'DATE': run_command('date', '+%Y-%m-%d', check: false, capture: true).stdout().strip(), 13 | }) 14 | 15 | man_src = [ 16 | 'grcli.1.md', 17 | 'grout.8.md', 18 | ] 19 | 20 | foreach m : man_src 21 | topic = m.split('.')[-3].split('/')[-1] 22 | section = m.split('.')[-2] 23 | output = '@0@.@1@'.format(topic, section) 24 | 25 | c = configure_file( 26 | input: m, 27 | output: '@0@.md'.format(output), 28 | configuration: config, 29 | ) 30 | custom_target( 31 | output, 32 | input: c, 33 | output: output, 34 | command: md2man, 35 | install: true, 36 | feed: true, 37 | capture: true, 38 | install_dir: '@0@/man@1@'.format(mandir, section), 39 | install_tag: 'man', 40 | ) 41 | endforeach 42 | -------------------------------------------------------------------------------- /frr/frr_plugin_install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # SPDX-License-Identifier: GPL-2.0-or-later 3 | # Copyright (c) 2025 Maxime Leroy, Free Mobile 4 | 5 | set -eu 6 | 7 | # $1 = built .so, $2 = destination .so, $3 = daemons file, $4 = stamp file 8 | install -D -m 755 "$1" "$2" 9 | sed -i -e '/^zebra_options=/ { 10 | /-M[[:space:]]*frr_dplane_grout/! s/"$/ -M frr_dplane_grout"/ 11 | }' "$3" 12 | touch "$4" 13 | -------------------------------------------------------------------------------- /frr/if_grout.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright (c) 2025 Maxime Leroy, Free Mobile 3 | 4 | #ifndef _IF_GROUT_H 5 | #define _IF_GROUT_H 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | enum zebra_dplane_result grout_add_del_address(struct zebra_dplane_ctx *ctx); 14 | 15 | void grout_interface_addr_dplane(struct gr_nexthop *gr_nh, bool new); 16 | void grout_link_change(struct gr_iface *gr_if, bool new, bool startup); 17 | 18 | #endif /* _IF_GROUT_H */ 19 | -------------------------------------------------------------------------------- /frr/log_grout.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright (c) 2025 Maxime Leroy, Free Mobile 3 | 4 | #ifndef _LOG_GROUT_H 5 | #define _LOG_GROUT_H 6 | 7 | #include 8 | #include 9 | 10 | extern unsigned long zebra_debug_dplane_grout; 11 | 12 | #define ZEBRA_DEBUG_DPLANE_GROUT 0x01 13 | #define IS_ZEBRA_DEBUG_DPLANE_GROUT (zebra_debug_dplane_grout & ZEBRA_DEBUG_DPLANE_GROUT) 14 | 15 | #define gr_log(priority, fmt, ...) zlog(priority, "GROUT: " fmt, ##__VA_ARGS__) 16 | #define gr_log_err(fmt, ...) gr_log(LOG_ERR, fmt, ##__VA_ARGS__) 17 | #define gr_log_debug(fmt, ...) \ 18 | do { \ 19 | if (IS_ZEBRA_DEBUG_DPLANE_GROUT) \ 20 | gr_log(LOG_DEBUG, "%s: " fmt, __func__, ##__VA_ARGS__); \ 21 | } while (0) 22 | 23 | #endif /* _LOG_GROUT_H */ 24 | -------------------------------------------------------------------------------- /frr/meson.build: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0-or-later 2 | # Copyright (c) 2025 Maxime Leroy, Free Mobile 3 | 4 | if not get_option('frr').enabled() 5 | subdir_done() 6 | endif 7 | 8 | frr_dep = dependency('frr', version: '>= 10.3', fallback: ['frr', 'frr_dep']) 9 | 10 | frr_plugin = shared_module( 11 | 'frr_dplane_grout', 12 | files( 13 | 'if_grout.c', 14 | 'rt_grout.c', 15 | 'zebra_dplane_grout.c', 16 | ), 17 | name_prefix: '', 18 | dependencies: [frr_dep], 19 | include_directories: api_inc + include_directories('.'), 20 | install: false, 21 | override_options: ['c_std=gnu11'], 22 | ) 23 | 24 | install_plugin = custom_target( 25 | 'install_frr_dplane_grout', 26 | output: '.install-stamp', 27 | command: [ 28 | files('frr_plugin_install.sh'), 29 | frr_plugin.full_path(), # compiled .so file 30 | frr_dep.get_variable('moduledir') / 'frr_dplane_grout.so', # target installation path 31 | frr_dep.get_variable('prefix') / 'etc/frr/daemons', # frr daemons config 32 | '@OUTPUT@', # stamp file 33 | ], 34 | depends: [frr_plugin], 35 | build_by_default: true, 36 | ) 37 | 38 | alias_target('frr_plugin_install', install_plugin) 39 | -------------------------------------------------------------------------------- /frr/rt_grout.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright (c) 2025 Maxime Leroy, Free Mobile 3 | 4 | #ifndef _RT_GROUT_H 5 | #define _RT_GROUT_H 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | void grout_route4_change(bool new, struct gr_ip4_route *gr_r4); 13 | void grout_route6_change(bool new, struct gr_ip6_route *gr_r6); 14 | enum zebra_dplane_result grout_add_del_route(struct zebra_dplane_ctx *ctx); 15 | 16 | #endif /* _RT_GROUT_H */ 17 | -------------------------------------------------------------------------------- /frr/zebra_dplane_grout.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright (c) 2025 Maxime Leroy, Free Mobile 3 | 4 | #ifndef _ZEBRA_DPLANE_GROUT_H 5 | #define _ZEBRA_DPLANE_GROUT_H 6 | 7 | #include 8 | #include 9 | 10 | int grout_client_send_recv(uint32_t req_type, size_t tx_len, const void *tx_data, void **rx_data); 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /licenses/BSD-3-clause.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2024 Robin Jarry 2 | 3 | The 3-Clause BSD License 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | 3. Neither the name of the copyright holder nor the names of its contributors 16 | may be used to endorse or promote products derived from this software 17 | without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 20 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /main/api.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2024 Robin Jarry 3 | 4 | #ifndef _GR_MAIN_API 5 | #define _GR_MAIN_API 6 | 7 | #include 8 | 9 | int api_socket_start(struct event_base *); 10 | void api_socket_stop(struct event_base *); 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /main/dpdk.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2023 Robin Jarry 3 | 4 | #ifndef _GR_CORE_DPDK 5 | #define _GR_CORE_DPDK 6 | 7 | int dpdk_log_init(void); 8 | int dpdk_init(void); 9 | void dpdk_fini(void); 10 | 11 | #endif 12 | -------------------------------------------------------------------------------- /main/event.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2025 Robin Jarry 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | STAILQ_HEAD(subscribers, gr_event_subscription); 12 | static struct subscribers subscribers = STAILQ_HEAD_INITIALIZER(subscribers); 13 | 14 | void gr_event_subscribe(struct gr_event_subscription *sub) { 15 | STAILQ_INSERT_TAIL(&subscribers, sub, next); 16 | } 17 | 18 | void gr_event_push(uint32_t ev_type, const void *obj) { 19 | const struct gr_event_subscription *sub; 20 | 21 | STAILQ_FOREACH (sub, &subscribers, next) { 22 | for (unsigned i = 0; i < sub->ev_count; i++) { 23 | if (sub->ev_types[i] == ev_type || sub->ev_types[i] == EVENT_TYPE_ALL) { 24 | sub->callback(ev_type, obj); 25 | break; 26 | } 27 | } 28 | } 29 | } 30 | 31 | STAILQ_HEAD(serializers, gr_event_serializer); 32 | static struct serializers serializers = STAILQ_HEAD_INITIALIZER(serializers); 33 | 34 | void gr_event_register_serializer(struct gr_event_serializer *serializer) { 35 | struct gr_event_serializer *s; 36 | 37 | if (serializer == NULL) 38 | ABORT("NULL serializer"); 39 | if (serializer->callback == NULL && serializer->size == 0) 40 | ABORT("one of callback or size are required"); 41 | if (serializer->callback != NULL && serializer->size != 0) 42 | ABORT("callback and size are mutually exclusive"); 43 | 44 | STAILQ_FOREACH (s, &serializers, next) { 45 | for (unsigned i = 0; i < s->ev_count; i++) { 46 | for (unsigned j = 0; j < serializer->ev_count; j++) { 47 | if (s->ev_types[i] == serializer->ev_types[j]) 48 | ABORT("duplicate serializer for event 0x%08x", 49 | serializer->ev_types[j]); 50 | } 51 | } 52 | } 53 | STAILQ_INSERT_TAIL(&serializers, serializer, next); 54 | } 55 | 56 | int gr_event_serialize(uint32_t ev_type, const void *obj, void **buf) { 57 | struct gr_event_serializer *s; 58 | 59 | STAILQ_FOREACH (s, &serializers, next) { 60 | for (unsigned i = 0; i < s->ev_count; i++) { 61 | if (s->ev_types[i] == ev_type) { 62 | if (s->callback != NULL) 63 | return s->callback(obj, buf); 64 | 65 | void *data = malloc(s->size); 66 | if (data == NULL) 67 | return errno_set(ENOMEM); 68 | 69 | memcpy(data, obj, s->size); 70 | *buf = data; 71 | 72 | return s->size; 73 | } 74 | } 75 | } 76 | ABORT("no registered serializer for event 0x%08x", ev_type); 77 | } 78 | -------------------------------------------------------------------------------- /main/gr_cmocka.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2024 Robin Jarry 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | #define mock_func(type, func, ...) \ 12 | type func; \ 13 | type func { \ 14 | __VA_ARGS__; \ 15 | return (type)mock(); \ 16 | } 17 | -------------------------------------------------------------------------------- /main/gr_config.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2023 Robin Jarry 3 | 4 | #ifndef _GR_CONFIG 5 | #define _GR_CONFIG 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | struct gr_config { 13 | const char *api_sock_path; 14 | uid_t api_sock_uid; 15 | gid_t api_sock_gid; 16 | mode_t api_sock_mode; 17 | unsigned log_level; 18 | bool test_mode; 19 | bool poll_mode; 20 | bool log_syslog; 21 | bool log_packets; 22 | char **eal_extra_args; 23 | cpu_set_t control_cpus; //!< control plane threads allowed CPUs 24 | cpu_set_t datapath_cpus; //!< datapath threads allowed CPUs 25 | }; 26 | 27 | extern struct gr_config gr_config; 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /main/gr_event.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2025 Robin Jarry 3 | 4 | #ifndef _GR_EVENT 5 | #define _GR_EVENT 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | typedef void (*gr_event_sub_cb_t)(uint32_t ev_type, const void *obj); 12 | 13 | struct gr_event_subscription { 14 | STAILQ_ENTRY(gr_event_subscription) next; 15 | gr_event_sub_cb_t callback; 16 | unsigned ev_count; 17 | uint32_t ev_types[/*ev_count*/]; 18 | }; 19 | 20 | // Register an event subscriber 21 | void gr_event_subscribe(struct gr_event_subscription *); 22 | 23 | // Notify all subscribers (see gr_event_subscribe) 24 | void gr_event_push(uint32_t ev_type, const void *obj); 25 | 26 | // Convert an event object to an API notification message 27 | // 28 | // @param[in] obj The control plane object associated with the event (may be NULL). 29 | // @param[out] buf Buffer malloc()ed by the callback, must be free()d by called. 30 | // @return The size of the allocated buffer or a negative error number. 31 | typedef int (*gr_event_serializer_cb_t)(const void *obj, void **buf); 32 | 33 | struct gr_event_serializer { 34 | STAILQ_ENTRY(gr_event_serializer) next; 35 | gr_event_serializer_cb_t callback; 36 | size_t size; 37 | unsigned ev_count; 38 | uint32_t ev_types[/*ev_count*/]; 39 | }; 40 | 41 | // Register a callback to convert an event object to an API notification message 42 | void gr_event_register_serializer(struct gr_event_serializer *); 43 | 44 | // Serialize an event to be sent to a subscribed client over the API socket. 45 | int gr_event_serialize(uint32_t ev_type, const void *obj, void **buf); 46 | 47 | #endif 48 | -------------------------------------------------------------------------------- /main/gr_log.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2023 Robin Jarry 3 | 4 | #ifndef _GR_CORE_LOG 5 | #define _GR_CORE_LOG 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | extern int gr_rte_log_type; 15 | #define RTE_LOGTYPE_GROUT gr_rte_log_type 16 | 17 | #define LOG(level, fmt, ...) \ 18 | do { \ 19 | RTE_LOG_CHECK_NO_NEWLINE(fmt); \ 20 | RTE_LOG(level, GROUT, "%s: " fmt "\n", __func__ __VA_OPT__(, ) __VA_ARGS__); \ 21 | } while (0) 22 | 23 | #define ABORT(fmt, ...) \ 24 | do { \ 25 | LOG(EMERG, fmt __VA_OPT__(, ) __VA_ARGS__); \ 26 | abort(); \ 27 | } while (0) 28 | 29 | static inline int __errno_log(int errnum, const char *func, const char *what) { 30 | RTE_LOG(ERR, GROUT, "%s: %s: %s (%d)\n", func, what, rte_strerror(errnum), errnum); 31 | return errno_set(errnum); 32 | } 33 | 34 | #define errno_log(err, what) __errno_log(err, __func__, what) 35 | 36 | static inline void *__errno_log_null(int errnum, const char *func, const char *what) { 37 | RTE_LOG(ERR, GROUT, "%s: %s: %s (%d)\n", func, what, rte_strerror(errnum), errnum); 38 | return errno_set_null(errnum); 39 | } 40 | 41 | #define errno_log_null(err, what) __errno_log_null(err, __func__, what) 42 | 43 | #endif 44 | -------------------------------------------------------------------------------- /main/gr_module.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2023 Robin Jarry 3 | 4 | #ifndef _GR_MODULE 5 | #define _GR_MODULE 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | struct api_out { 13 | uint32_t status; 14 | uint32_t len; 15 | }; 16 | 17 | static inline struct api_out api_out(uint32_t status, uint32_t len) { 18 | struct api_out out = {status, len}; 19 | return out; 20 | } 21 | 22 | typedef struct api_out (*gr_api_handler_func)(const void *request, void **response); 23 | 24 | struct gr_api_handler { 25 | const char *name; 26 | uint32_t request_type; 27 | gr_api_handler_func callback; 28 | STAILQ_ENTRY(gr_api_handler) entries; 29 | }; 30 | 31 | void gr_register_api_handler(struct gr_api_handler *); 32 | 33 | struct gr_module { 34 | const char *name; 35 | const char *depends_on; 36 | void (*init)(struct event_base *); 37 | void (*fini)(struct event_base *); 38 | void (*init_dp)(void); 39 | void (*fini_dp)(void); 40 | STAILQ_ENTRY(gr_module) entries; 41 | }; 42 | 43 | void gr_register_module(struct gr_module *); 44 | 45 | void gr_modules_dp_init(void); 46 | 47 | void gr_modules_dp_fini(void); 48 | 49 | #endif 50 | -------------------------------------------------------------------------------- /main/gr_queue.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 1991, 1993 The Regents of the University of California 3 | // Copyright (c) 2023 Robin Jarry 4 | 5 | #ifndef _GR_QUEUE 6 | #define _GR_QUEUE 7 | 8 | #include 9 | 10 | // These macros were imported from OpenBSD 7.4 sys/queue.h 11 | 12 | #ifndef SLIST_FOREACH_SAFE 13 | #define SLIST_FOREACH_SAFE(var, head, field, tvar) \ 14 | for ((var) = SLIST_FIRST(head); (var) && ((tvar) = SLIST_NEXT(var, field), 1); \ 15 | (var) = (tvar)) 16 | #endif 17 | 18 | #ifndef LIST_FOREACH_SAFE 19 | #define LIST_FOREACH_SAFE(var, head, field, tvar) \ 20 | for ((var) = LIST_FIRST(head); (var) && ((tvar) = LIST_NEXT(var, field), 1); (var) = (tvar)) 21 | #endif 22 | 23 | #ifndef STAILQ_FOREACH_SAFE 24 | #define STAILQ_FOREACH_SAFE(var, head, field, tvar) \ 25 | for ((var) = STAILQ_FIRST(head); (var) && ((tvar) = STAILQ_NEXT(var, field), 1); \ 26 | (var) = (tvar)) 27 | #endif 28 | 29 | #ifndef TAILQ_FOREACH_SAFE 30 | #define TAILQ_FOREACH_SAFE(var, head, field, tvar) \ 31 | for ((var) = TAILQ_FIRST(head); \ 32 | (var) != TAILQ_END(head) && ((tvar) = TAILQ_NEXT(var, field), 1); \ 33 | (var) = (tvar)) 34 | #endif 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /main/grout.bash-completion: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # SPDX-License-Identifier: BSD-3-Clause 3 | # Copyright (c) 2024 Robin Jarry 4 | 5 | _grout() { 6 | local cur prev words cword split 7 | local o w opts 8 | local -a all opt 9 | _init_completion -s -n : || return 10 | 11 | all=( 12 | "-h --help" 13 | "-p --poll-mode" 14 | "-t --test-mode" 15 | "-v --verbose" 16 | "-s --socket" 17 | "-x --trace-packets" 18 | ) 19 | case "$prev" in 20 | -s|--socket) 21 | _filedir 22 | return 23 | ;; 24 | esac 25 | 26 | opts="" 27 | for o in "${all[@]}"; do 28 | opt=($o) 29 | for w in "${words[@]}"; do 30 | case "$w" in 31 | "${opt[0]}"|"${opt[1]}") 32 | continue 2 33 | ;; 34 | esac 35 | done 36 | opts="$opts ${opt[*]}" 37 | done 38 | 39 | COMPREPLY=($(compgen -W "$opts" -- $cur)) 40 | } 41 | complete -F _grout grout 42 | -------------------------------------------------------------------------------- /main/grout.default: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Copyright (c) 2024 Robin Jarry 3 | # vim: ft=sh 4 | 5 | # Add grout command line flags here and restart grout.service. 6 | GROUT_OPTS='--syslog' 7 | -------------------------------------------------------------------------------- /main/grout.init: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Copyright (c) 2024 Robin Jarry 3 | 4 | # This file is piped into 'grcli -xe' after grout has started successfully. 5 | # Uncomment the examples below and/or add your own commands. 6 | 7 | # Datapath CPU affinity 8 | #set affinity cpus control 0,1 datapath 2,22,4,24 9 | 10 | # Physical ports creation 11 | #add interface port p0 devargs 0000:4b:00.0 rxqs 2 12 | #add interface port p1 devargs 0000:4b:00.1 rxqs 2 13 | 14 | # Interface addresses 15 | #add ip address 1.2.3.4/24 iface p0 16 | #add ip address 4.3.2.1/24 iface p1 17 | 18 | # Default route 19 | #add ip route 0.0.0.0/0 via 1.2.3.254 20 | -------------------------------------------------------------------------------- /main/grout.service: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Copyright (c) 2024 Robin Jarry 3 | # ex: ft=systemd 4 | 5 | [Unit] 6 | Description=Graph router daemon 7 | After=network.target basic.target 8 | 9 | [Service] 10 | Type=notify 11 | Restart=on-failure 12 | EnvironmentFile=-/etc/default/grout 13 | ExecStartPre=/usr/bin/udevadm settle 14 | ExecStart=/usr/bin/grout $GROUT_OPTS 15 | ExecStartPost=/usr/bin/grcli -xef /etc/grout.init 16 | 17 | [Install] 18 | WantedBy=multi-user.target 19 | -------------------------------------------------------------------------------- /main/meson.build: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Copyright (c) 2023 Robin Jarry 3 | 4 | src += files( 5 | 'api.c', 6 | 'dpdk.c', 7 | 'event.c', 8 | 'main.c', 9 | 'module.c', 10 | 'sd_notify.c', 11 | 'signals.c', 12 | ) 13 | 14 | inc += include_directories('.') 15 | 16 | tests += [ 17 | { 18 | 'sources': files('vec_test.c'), 19 | 'link_args': [], 20 | }, 21 | ] 22 | -------------------------------------------------------------------------------- /main/module.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2023 Robin Jarry 3 | 4 | #ifndef _GR_CONTROL_PRIV 5 | #define _GR_CONTROL_PRIV 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | const struct gr_api_handler *lookup_api_handler(const struct gr_api_request *); 13 | 14 | void modules_init(struct event_base *); 15 | 16 | void modules_fini(struct event_base *); 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /main/sd_notify.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2024 Robin Jarry 3 | // 4 | // This code is heavily inspired from: 5 | // https://www.freedesktop.org/software/systemd/man/devel/sd_notify.html#Standalone%20Implementations 6 | 7 | // SPDX-License-Identifier: MIT-0 8 | // 9 | // Implement the systemd notify protocol without external dependencies. 10 | // Supports both readiness notification on startup and on reloading, 11 | // according to the protocol defined at: 12 | // https://www.freedesktop.org/software/systemd/man/latest/sd_notify.html 13 | // This protocol is guaranteed to be stable as per: 14 | // https://systemd.io/PORTABILITY_AND_STABILITY/ 15 | 16 | #include "sd_notify.h" 17 | 18 | #include 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | static void closefd(int *fd) { 32 | if (fd != NULL && *fd >= 0) { 33 | close(*fd); 34 | *fd = -1; 35 | } 36 | } 37 | 38 | int sd_notifyf(int unset_environment, const char *format, ...) { 39 | struct sockaddr_un sun = {.sun_family = AF_UNIX}; 40 | __attribute__((cleanup(closefd))) int fd = -1; 41 | const char *sock_path; 42 | char msg[BUFSIZ]; 43 | size_t msg_len; 44 | va_list ap; 45 | ssize_t n; 46 | 47 | // Verify the argument first 48 | if (format == NULL) 49 | return errno_set(EINVAL); 50 | 51 | va_start(ap, format); 52 | msg_len = vsnprintf(msg, sizeof(msg), format, ap); 53 | va_end(ap); 54 | 55 | if (msg_len <= 0) 56 | return errno_set(EINVAL); 57 | 58 | // If the variable is not set, the protocol is a noop 59 | if ((sock_path = getenv("NOTIFY_SOCKET")) == NULL) 60 | return 0; 61 | 62 | // Only AF_UNIX is supported, with path or abstract sockets 63 | if (sock_path[0] != '/' && sock_path[0] != '@') 64 | return errno_set(EAFNOSUPPORT); 65 | 66 | // Ensure there is room for NUL byte 67 | if (strlen(sock_path) >= sizeof(sun.sun_path)) 68 | return errno_set(E2BIG); 69 | 70 | memccpy(sun.sun_path, sock_path, 0, sizeof(sun.sun_path)); 71 | 72 | // Support for abstract socket 73 | if (sun.sun_path[0] == '@') 74 | sun.sun_path[0] = 0; 75 | 76 | if ((fd = socket(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0)) < 0) 77 | return -errno; 78 | 79 | if (connect(fd, &sun, sizeof(sun)) != 0) 80 | return -errno; 81 | 82 | if ((n = write(fd, msg, msg_len)) != (ssize_t)msg_len) 83 | return errno_set(n < 0 ? errno : EPROTO); 84 | 85 | if (unset_environment != 0 && unsetenv("NOTIFY_SOCKET") < 0) 86 | return -errno; 87 | 88 | return 1; // Notified! 89 | } 90 | -------------------------------------------------------------------------------- /main/sd_notify.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2024 Robin Jarry 3 | 4 | #ifndef _GR_SD_NOTIFY_H 5 | #define _GR_SD_NOTIFY_H 6 | 7 | // Implement the systemd notify protocol without external dependencies. 8 | int sd_notifyf(int unset_environment, const char *format, ...); 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /main/signals.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2023 Robin Jarry 3 | 4 | #include "signals.h" 5 | 6 | #include 7 | 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | static void signal_cb(evutil_socket_t sig, short /*what*/, void *priv) { 14 | struct event_base *base = priv; 15 | 16 | LOG(NOTICE, "received signal SIG%s", sigabbrev_np(sig)); 17 | 18 | switch (sig) { 19 | case SIGPIPE: 20 | case SIGCHLD: 21 | // ignore 22 | break; 23 | default: 24 | event_base_loopexit(base, NULL); 25 | } 26 | } 27 | 28 | static struct event *ev_sigint; 29 | static struct event *ev_sigquit; 30 | static struct event *ev_sigterm; 31 | static struct event *ev_sigchld; 32 | static struct event *ev_sigpipe; 33 | 34 | int register_signals(struct event_base *base) { 35 | unregister_signals(); 36 | 37 | ev_sigint = evsignal_new(base, SIGINT, signal_cb, base); 38 | if (ev_sigint == NULL || event_add(ev_sigint, NULL) < 0) 39 | return errno_set(ENOMEM); 40 | 41 | ev_sigterm = evsignal_new(base, SIGTERM, signal_cb, base); 42 | if (ev_sigterm == NULL || event_add(ev_sigterm, NULL) < 0) 43 | return errno_set(ENOMEM); 44 | 45 | ev_sigquit = evsignal_new(base, SIGQUIT, signal_cb, base); 46 | if (ev_sigquit == NULL || event_add(ev_sigquit, NULL) < 0) 47 | return errno_set(ENOMEM); 48 | 49 | ev_sigchld = evsignal_new(base, SIGCHLD, signal_cb, base); 50 | if (ev_sigchld == NULL || event_add(ev_sigchld, NULL) < 0) 51 | return errno_set(ENOMEM); 52 | 53 | ev_sigpipe = evsignal_new(base, SIGPIPE, signal_cb, base); 54 | if (ev_sigpipe == NULL || event_add(ev_sigpipe, NULL) < 0) 55 | return errno_set(ENOMEM); 56 | 57 | return 0; 58 | } 59 | 60 | void unregister_signals(void) { 61 | if (ev_sigpipe != NULL) 62 | event_free(ev_sigpipe); 63 | if (ev_sigchld != NULL) 64 | event_free(ev_sigchld); 65 | if (ev_sigint != NULL) 66 | event_free(ev_sigint); 67 | if (ev_sigquit != NULL) 68 | event_free(ev_sigquit); 69 | if (ev_sigterm != NULL) 70 | event_free(ev_sigterm); 71 | ev_sigpipe = NULL; 72 | ev_sigchld = NULL; 73 | ev_sigterm = NULL; 74 | ev_sigquit = NULL; 75 | ev_sigint = NULL; 76 | } 77 | -------------------------------------------------------------------------------- /main/signals.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2023 Robin Jarry 3 | 4 | #ifndef _GR_SIGNALS 5 | #define _GR_SIGNALS 6 | 7 | #include 8 | 9 | int register_signals(struct event_base *base); 10 | void unregister_signals(void); 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /main/vec_test.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2024 Robin Jarry 3 | 4 | #include 5 | #include 6 | 7 | static void int_vec(void **) { 8 | int *vec = NULL; 9 | 10 | assert_int_equal(gr_vec_len(vec), 0); 11 | 12 | for (int i = 0; i < 5; i++) 13 | gr_vec_add(vec, i); 14 | 15 | assert_int_equal(gr_vec_len(vec), 5); 16 | 17 | for (int i = 0; i < 5; i++) 18 | assert_int_equal(vec[i], i); 19 | 20 | gr_vec_insert(vec, 1, 42); 21 | assert_int_equal(gr_vec_len(vec), 6); 22 | assert_int_equal(vec[0], 0); 23 | assert_int_equal(vec[1], 42); 24 | assert_int_equal(vec[2], 1); 25 | assert_int_equal(vec[3], 2); 26 | assert_int_equal(vec[4], 3); 27 | assert_int_equal(vec[5], 4); 28 | 29 | gr_vec_del_swap(vec, 3); 30 | assert_int_equal(gr_vec_len(vec), 5); 31 | 32 | assert_int_equal(gr_vec_pop(vec), 3); 33 | assert_int_equal(gr_vec_pop(vec), 4); 34 | assert_int_equal(gr_vec_pop(vec), 1); 35 | assert_int_equal(gr_vec_pop(vec), 42); 36 | 37 | gr_vec_del_swap(vec, 0); 38 | assert_int_equal(gr_vec_len(vec), 0); 39 | 40 | gr_vec_free(vec); 41 | } 42 | 43 | static void str_vec(void **) { 44 | const char **vec = NULL; 45 | 46 | assert_int_equal(gr_vec_len(vec), 0); 47 | 48 | gr_vec_add(vec, "foo"); 49 | gr_vec_add(vec, "bar"); 50 | gr_vec_add(vec, "baz"); 51 | 52 | assert_int_equal(gr_vec_len(vec), 3); 53 | assert_string_equal(vec[0], "foo"); 54 | assert_string_equal(vec[1], "bar"); 55 | assert_string_equal(vec[2], "baz"); 56 | 57 | gr_vec_del(vec, 1); 58 | 59 | assert_int_equal(gr_vec_len(vec), 2); 60 | assert_string_equal(vec[0], "foo"); 61 | assert_string_equal(vec[1], "baz"); 62 | 63 | gr_vec_insert(vec, 2, "bar"); 64 | assert_int_equal(gr_vec_len(vec), 3); 65 | assert_string_equal(vec[0], "foo"); 66 | assert_string_equal(vec[1], "baz"); 67 | assert_string_equal(vec[2], "bar"); 68 | 69 | gr_vec_del_swap(vec, 0); 70 | assert_int_equal(gr_vec_len(vec), 2); 71 | assert_string_equal(vec[0], "bar"); 72 | assert_string_equal(vec[1], "baz"); 73 | 74 | gr_vec_free(vec); 75 | } 76 | 77 | int main(void) { 78 | const struct CMUnitTest tests[] = { 79 | cmocka_unit_test(int_vec), 80 | cmocka_unit_test(str_vec), 81 | }; 82 | return cmocka_run_group_tests(tests, NULL, NULL); 83 | } 84 | -------------------------------------------------------------------------------- /meson_options.txt: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Copyright (c) 2024 Vincent Jardin 3 | 4 | option( 5 | 'docs', type: 'feature', value: 'auto', 6 | description: 'Build man pages. If set to "auto", only build if go-md2man is found.', 7 | ) 8 | 9 | option( 10 | 'frr', type: 'feature', value: 'disabled', 11 | description: 'Build FRR plugin. If set to "auto", only build if FRR headers are found.', 12 | ) 13 | 14 | option( 15 | 'tests', type: 'feature', value: 'auto', 16 | description: 'Build unit-tests. If set to "auto", only build if cmocka is found.', 17 | ) 18 | -------------------------------------------------------------------------------- /modules/infra/api/graph.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2024 Robin Jarry 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | static struct api_out graph_dump(const void *request, void **response) { 15 | const struct gr_infra_graph_dump_req *req = request; 16 | char *buf = NULL, *copy, *line, *prev_line, *eol; 17 | struct gr_infra_graph_dump_resp *resp; 18 | size_t buf_len = 0, resp_len = 0; 19 | const char *graph_name; 20 | struct worker *worker; 21 | FILE *stream = NULL; 22 | int ret = 0; 23 | 24 | if (req->flags & ~GR_INFRA_GRAPH_DUMP_F_ERRORS) 25 | return api_out(EINVAL, 0); 26 | 27 | STAILQ_FOREACH (worker, &workers, next) { 28 | for (int i = 0; i < 2; i++) { 29 | if (worker->graph[i] != NULL) { 30 | graph_name = worker->graph[i]->name; 31 | goto found; 32 | } 33 | } 34 | } 35 | return api_out(ENODEV, 0); 36 | found: 37 | if ((stream = open_memstream(&buf, &buf_len)) == NULL) 38 | return api_out(errno, 0); 39 | 40 | if ((ret = rte_graph_export(graph_name, stream)) < 0) 41 | goto end; 42 | fflush(stream); 43 | resp_len = sizeof(*resp) + buf_len; 44 | if ((resp = calloc(1, resp_len)) == NULL) { 45 | ret = -ENOMEM; 46 | resp_len = 0; 47 | goto end; 48 | } 49 | 50 | copy = resp->dot; 51 | prev_line = NULL; 52 | line = buf; 53 | 54 | while ((eol = strchr(line, '\n')) != NULL) { 55 | *eol = '\0'; 56 | // Remove sink nodes from the output. They all have a "darkorange" color. 57 | // Also remove non-sink nodes that contain "error" in their name. 58 | bool is_err = strstr(line, "darkorange") || strstr(line, "error"); 59 | // Remove duplicate lines. 60 | bool is_dup = prev_line != NULL && strncmp(line, prev_line, strlen(line)) == 0; 61 | if (!is_dup && (req->flags & GR_INFRA_GRAPH_DUMP_F_ERRORS || !is_err)) { 62 | *eol = '\n'; // restore newline char 63 | copy = memccpy(copy, line, '\n', eol - line + 1); 64 | } 65 | prev_line = line; 66 | line = eol + 1; 67 | } 68 | 69 | resp->len = copy - resp->dot; 70 | resp_len = sizeof(*resp) + resp->len; 71 | *response = resp; 72 | end: 73 | fclose(stream); 74 | free(buf); 75 | return api_out(-ret, resp_len); 76 | } 77 | 78 | static struct gr_api_handler graph_dump_handler = { 79 | .name = "graph dump", 80 | .request_type = GR_INFRA_GRAPH_DUMP, 81 | .callback = graph_dump, 82 | }; 83 | 84 | RTE_INIT(graph_init) { 85 | gr_register_api_handler(&graph_dump_handler); 86 | } 87 | -------------------------------------------------------------------------------- /modules/infra/api/meson.build: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Copyright (c) 2024 Robin Jarry 3 | 4 | src += files( 5 | 'affinity.c', 6 | 'graph.c', 7 | 'iface.c', 8 | 'nexthop.c', 9 | 'stats.c', 10 | 'trace.c', 11 | ) 12 | 13 | api_headers += files( 14 | 'gr_infra.h', 15 | 'gr_nexthop.h', 16 | ) 17 | api_inc += include_directories('.') 18 | -------------------------------------------------------------------------------- /modules/infra/api/nexthop.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2025 Robin Jarry 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | static struct api_out nh_config_get(const void * /*request*/, void **response) { 9 | struct gr_infra_nh_config_get_resp *resp = malloc(sizeof(*resp)); 10 | 11 | if (resp == NULL) 12 | return api_out(ENOMEM, 0); 13 | 14 | resp->base = nh_conf; 15 | resp->used_count = nexthop_used_count(); 16 | *response = resp; 17 | 18 | return api_out(0, sizeof(*resp)); 19 | } 20 | 21 | static struct api_out nh_config_set(const void *request, void ** /*response*/) { 22 | const struct gr_infra_nh_config_set_req *req = request; 23 | return api_out(-nexthop_config_set(&req->base), 0); 24 | } 25 | 26 | static struct gr_api_handler get_handler = { 27 | .name = "nh config get", 28 | .request_type = GR_INFRA_NH_CONFIG_GET, 29 | .callback = nh_config_get, 30 | }; 31 | 32 | static struct gr_api_handler set_handler = { 33 | .name = "nh config set", 34 | .request_type = GR_INFRA_NH_CONFIG_SET, 35 | .callback = nh_config_set, 36 | }; 37 | 38 | RTE_INIT(trace_init) { 39 | gr_register_api_handler(&get_handler); 40 | gr_register_api_handler(&set_handler); 41 | } 42 | -------------------------------------------------------------------------------- /modules/infra/cli/gr_cli_iface.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2024 Robin Jarry 3 | 4 | #ifndef _GR_CLI_IFACE 5 | #define _GR_CLI_IFACE 6 | 7 | #include 8 | 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | struct cli_iface_type { 15 | STAILQ_ENTRY(cli_iface_type) next; 16 | gr_iface_type_t type_id; 17 | const char *name; 18 | void (*show)(const struct gr_api_client *c, const struct gr_iface *); 19 | void (*list_info)(const struct gr_api_client *c, const struct gr_iface *, char *, size_t); 20 | }; 21 | 22 | void register_iface_type(struct cli_iface_type *); 23 | 24 | const struct cli_iface_type *type_from_name(const char *name); 25 | const struct cli_iface_type *type_from_id(gr_iface_type_t type_id); 26 | int iface_from_name(const struct gr_api_client *c, const char *name, struct gr_iface *iface); 27 | int iface_from_id(const struct gr_api_client *c, uint16_t ifid, struct gr_iface *iface); 28 | 29 | struct ec_node; 30 | struct ec_comp; 31 | 32 | int complete_iface_types( 33 | const struct gr_api_client *c, 34 | const struct ec_node *node, 35 | struct ec_comp *comp, 36 | const char *arg, 37 | void *cb_arg 38 | ); 39 | int complete_iface_names( 40 | const struct gr_api_client *c, 41 | const struct ec_node *node, 42 | struct ec_comp *comp, 43 | const char *arg, 44 | void *cb_arg 45 | ); 46 | 47 | #define INT2PTR(i) (void *)(uintptr_t)(i) 48 | 49 | #define IFACE_ATTRS_CMD "(up|down),(promisc PROMISC),(allmulti ALLMULTI),(mtu MTU),(vrf VRF)" 50 | 51 | #define IFACE_ATTRS_ARGS \ 52 | with_help("Set the interface UP.", ec_node_str("up", "up")), \ 53 | with_help("Enable/disable promiscuous mode.", ec_node_re("PROMISC", "on|off")), \ 54 | with_help("Enable/disable all-multicast mode.", ec_node_re("ALLMULTI", "on|off")), \ 55 | with_help("Set the interface DOWN.", ec_node_str("down", "down")), \ 56 | with_help( \ 57 | "Maximum transmission unit size.", \ 58 | ec_node_uint("MTU", 1280, UINT16_MAX - 1, 10) \ 59 | ), \ 60 | with_help( \ 61 | "L3 addressing/routing domain ID.", \ 62 | ec_node_uint("VRF", 0, UINT16_MAX - 1, 10) \ 63 | ) 64 | 65 | uint64_t parse_iface_args( 66 | const struct gr_api_client *c, 67 | const struct ec_pnode *p, 68 | struct gr_iface *iface, 69 | bool update 70 | ); 71 | 72 | #endif 73 | -------------------------------------------------------------------------------- /modules/infra/cli/graph.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2024 Robin Jarry 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | static cmd_status_t graph_dump(const struct gr_api_client *c, const struct ec_pnode *p) { 15 | struct gr_infra_graph_dump_req req = {.flags = 0}; 16 | const struct gr_infra_graph_dump_resp *resp; 17 | void *resp_ptr = NULL; 18 | 19 | if (arg_str(p, "full")) 20 | req.flags |= GR_INFRA_GRAPH_DUMP_F_ERRORS; 21 | 22 | if (gr_api_client_send_recv(c, GR_INFRA_GRAPH_DUMP, sizeof(req), &req, &resp_ptr) < 0) 23 | return CMD_ERROR; 24 | 25 | resp = resp_ptr; 26 | fwrite(resp->dot, 1, resp->len, stdout); 27 | free(resp_ptr); 28 | 29 | return CMD_SUCCESS; 30 | } 31 | 32 | static int ctx_init(struct ec_node *root) { 33 | int ret; 34 | 35 | ret = CLI_COMMAND( 36 | CLI_CONTEXT(root, CTX_SHOW), 37 | "graph [brief|full]", 38 | graph_dump, 39 | "Show packet processing graph info (requires interfaces to be configured).", 40 | with_help("Hide error nodes (default).", ec_node_str("brief", "brief")), 41 | with_help("Show all nodes.", ec_node_str("full", "full")) 42 | ); 43 | if (ret < 0) 44 | return ret; 45 | 46 | return 0; 47 | } 48 | 49 | static struct gr_cli_context ctx = { 50 | .name = "graph", 51 | .init = ctx_init, 52 | }; 53 | 54 | static void __attribute__((constructor, used)) init(void) { 55 | register_context(&ctx); 56 | } 57 | -------------------------------------------------------------------------------- /modules/infra/cli/loopback.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2024 Christophe Fontaine 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | static void loopback_show(const struct gr_api_client *, const struct gr_iface *) { } 10 | 11 | static void 12 | loopback_list_info(const struct gr_api_client *, const struct gr_iface *, char *, size_t) { } 13 | 14 | static struct cli_iface_type loopback_type = { 15 | .type_id = GR_IFACE_TYPE_LOOPBACK, 16 | .name = "loopback", 17 | .show = loopback_show, 18 | .list_info = loopback_list_info, 19 | }; 20 | 21 | static void __attribute__((constructor, used)) init(void) { 22 | register_iface_type(&loopback_type); 23 | } 24 | -------------------------------------------------------------------------------- /modules/infra/cli/meson.build: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Copyright (c) 2023 Robin Jarry 3 | 4 | cli_src += files( 5 | 'affinity.c', 6 | 'events.c', 7 | 'graph.c', 8 | 'iface.c', 9 | 'loopback.c', 10 | 'nexthop.c', 11 | 'port.c', 12 | 'stats.c', 13 | 'trace.c', 14 | 'vlan.c', 15 | ) 16 | 17 | cli_inc += include_directories('.') 18 | -------------------------------------------------------------------------------- /modules/infra/control/gr_graph.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2024 Robin Jarry 3 | 4 | #ifndef _GR_INFRA_GRAPH 5 | #define _GR_INFRA_GRAPH 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | #ifdef __GROUT_UNIT_TEST__ 15 | #include 16 | 17 | // The function is defined as static inline in the original code, so it cannot be wrapped directly 18 | // using CMocka's function wrapping mechanism. 19 | #define rte_node_enqueue_x1 rte_node_enqueue_x1_real // Rename before including 20 | #include 21 | #undef rte_node_enqueue_x1 22 | 23 | static inline void 24 | rte_node_enqueue_x1(struct rte_graph *, struct rte_node *, rte_edge_t next, void *) { 25 | check_expected(next); 26 | } 27 | #else 28 | #include 29 | #endif 30 | 31 | void *gr_node_data_get(const char *graph, const char *node); 32 | 33 | int gr_node_data_set(const char *graph, const char *node, void *data); 34 | 35 | rte_edge_t gr_node_attach_parent(const char *parent, const char *node); 36 | 37 | uint16_t drop_packets(struct rte_graph *, struct rte_node *, void **, uint16_t); 38 | int drop_format(char *buf, size_t buf_len, const void *data, size_t data_len); 39 | 40 | typedef void (*gr_node_register_cb_t)(void); 41 | 42 | struct gr_node_info { 43 | struct rte_node_register *node; 44 | gr_node_register_cb_t register_callback; 45 | gr_node_register_cb_t unregister_callback; 46 | gr_trace_format_cb_t trace_format; 47 | STAILQ_ENTRY(gr_node_info) next; 48 | }; 49 | 50 | const struct gr_node_info *gr_node_info_get(rte_node_t node_id); 51 | 52 | STAILQ_HEAD(node_infos, gr_node_info); 53 | extern struct node_infos node_infos; 54 | 55 | #define GR_NODE_REGISTER(info) \ 56 | RTE_INIT(gr_node_register_##info) { \ 57 | STAILQ_INSERT_TAIL(&node_infos, &info, next); \ 58 | } 59 | 60 | #define GR_DROP_REGISTER(node_name) \ 61 | static struct rte_node_register drop_node_##node_name = { \ 62 | .name = #node_name, \ 63 | .process = drop_packets, \ 64 | }; \ 65 | static struct gr_node_info drop_info_##node_name = { \ 66 | .node = &drop_node_##node_name, \ 67 | .trace_format = drop_format, \ 68 | }; \ 69 | RTE_INIT(gr_drop_register_##node_name) { \ 70 | STAILQ_INSERT_TAIL(&node_infos, &drop_info_##node_name, next); \ 71 | } 72 | 73 | #endif 74 | -------------------------------------------------------------------------------- /modules/infra/control/gr_iface.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2024 Robin Jarry 3 | 4 | #ifndef _GR_INFRA_IFACE 5 | #define _GR_INFRA_IFACE 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | struct __rte_cache_aligned iface { 17 | BASE(__gr_iface_base); 18 | 19 | const struct iface **subinterfaces; 20 | char *name; 21 | alignas(alignof(void *)) uint8_t info[/* size depends on type */]; 22 | }; 23 | 24 | #define IFACE_SET_ALL UINT64_C(0xffffffffffffffff) 25 | 26 | typedef int (*iface_init_t)(struct iface *, const void *api_info); 27 | typedef int (*iface_reconfig_t)( 28 | struct iface *, 29 | uint64_t set_attrs, 30 | const struct gr_iface *, 31 | const void *api_info 32 | ); 33 | typedef int (*iface_fini_t)(struct iface *); 34 | typedef int (*iface_eth_addr_get_t)(const struct iface *, struct rte_ether_addr *); 35 | typedef int (*iface_eth_addr_filter_t)(struct iface *, const struct rte_ether_addr *); 36 | typedef void (*iface_to_api_t)(void *api_info, const struct iface *); 37 | 38 | struct iface_type { 39 | uint16_t id; 40 | size_t info_size; 41 | iface_init_t init; 42 | iface_reconfig_t reconfig; 43 | iface_fini_t fini; 44 | iface_eth_addr_get_t get_eth_addr; 45 | iface_eth_addr_filter_t add_eth_addr; 46 | iface_eth_addr_filter_t del_eth_addr; 47 | iface_to_api_t to_api; 48 | const char *name; 49 | STAILQ_ENTRY(iface_type) next; 50 | }; 51 | 52 | void iface_type_register(struct iface_type *); 53 | struct iface_type *iface_type_get(gr_iface_type_t type_id); 54 | struct iface *iface_create(const struct gr_iface *conf, const void *api_info); 55 | int iface_reconfig( 56 | uint16_t ifid, 57 | uint64_t set_attrs, 58 | const struct gr_iface *conf, 59 | const void *api_info 60 | ); 61 | int iface_destroy(uint16_t ifid); 62 | struct iface *iface_from_id(uint16_t ifid); 63 | void iface_add_subinterface(struct iface *parent, const struct iface *sub); 64 | void iface_del_subinterface(struct iface *parent, const struct iface *sub); 65 | int iface_get_eth_addr(uint16_t ifid, struct rte_ether_addr *); 66 | int iface_add_eth_addr(uint16_t ifid, const struct rte_ether_addr *); 67 | int iface_del_eth_addr(uint16_t ifid, const struct rte_ether_addr *); 68 | uint16_t ifaces_count(gr_iface_type_t type_id); 69 | struct iface *iface_next(gr_iface_type_t type_id, const struct iface *prev); 70 | 71 | struct iface *get_vrf_iface(uint16_t vrf_id); 72 | struct iface *iface_loopback_create(uint16_t vrf_id); 73 | int iface_loopback_delete(uint16_t vrf_id); 74 | 75 | #define MAX_IFACES 1024 76 | #define MAX_VRFS 256 77 | 78 | #endif 79 | -------------------------------------------------------------------------------- /modules/infra/control/gr_loopback.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2024 Christophe Fontaine 3 | 4 | #ifndef _LOOPBACK 5 | #define _LOOPBACK 6 | 7 | #include 8 | 9 | #include 10 | 11 | void loopback_tx(struct rte_mbuf *m); 12 | control_input_t loopback_get_control_id(void); 13 | void loopback_input_add_type(rte_be16_t eth_type, const char *next_node); 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /modules/infra/control/gr_mempool.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2024 Christophe Fontaine 3 | 4 | #ifndef _GR_MEMPOOL 5 | #define _GR_MEMPOOL 6 | 7 | #include 8 | 9 | struct rte_mempool *gr_pktmbuf_pool_get(int8_t socket_id, uint32_t count); 10 | void gr_pktmbuf_pool_release(struct rte_mempool *mp, uint32_t count); 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /modules/infra/control/gr_nh_control.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2024 Robin Jarry 3 | 4 | #ifndef _GR_INFRA_NH_CONTROL 5 | #define _GR_INFRA_NH_CONTROL 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | extern struct gr_nexthop_config nh_conf; 15 | 16 | int nexthop_config_set(const struct gr_nexthop_config *); 17 | unsigned nexthop_used_count(void); 18 | 19 | struct __rte_cache_aligned nexthop { 20 | BASE(gr_nexthop); 21 | 22 | clock_t last_request; 23 | 24 | uint8_t ucast_probes; 25 | uint8_t bcast_probes; 26 | uint32_t ref_count; // number of routes referencing this nexthop 27 | 28 | // packets waiting for address resolution 29 | struct rte_mbuf *held_pkts_head; 30 | struct rte_mbuf *held_pkts_tail; 31 | }; 32 | 33 | struct hoplist { 34 | // list managed with gr_vec_* 35 | struct nexthop **nh; 36 | }; 37 | 38 | // Lookup a nexthop from the global pool that matches the specified criteria. 39 | struct nexthop * 40 | nexthop_lookup(gr_nh_type_t type, uint16_t vrf_id, uint16_t iface_id, const void *addr); 41 | 42 | // Compare two nexthops, return True if the same, else False 43 | bool nexthop_equal(const struct nexthop *, const struct nexthop *); 44 | 45 | // Allocate a new nexthop from the global pool with the provided initial values. 46 | struct nexthop * 47 | nexthop_new(gr_nh_type_t type, uint16_t vrf_id, uint16_t iface_id, const void *addr); 48 | 49 | // Increment the reference counter of a nexthop. 50 | void nexthop_incref(struct nexthop *); 51 | 52 | // Decrement the reference counter of a nexthop. 53 | // When the counter drops to 0, the nexthop is marked as available in the global pool. 54 | void nexthop_decref(struct nexthop *); 55 | 56 | // Callback for nh_iter(). 57 | typedef void (*nh_iter_cb_t)(struct nexthop *nh, void *priv); 58 | 59 | // Iterate over all nexthops and invoke a callback for each active nexthop. 60 | void nexthop_iter(nh_iter_cb_t nh_cb, void *priv); 61 | 62 | struct nexthop_ops { 63 | // Callback that will be invoked when a nexthop needs to be refreshed by sending a probe. 64 | int (*solicit)(struct nexthop *); 65 | // Callback that will be invoked when all nexthop probes failed and it needs to be freed. 66 | void (*free)(struct nexthop *); 67 | }; 68 | 69 | void nexthop_ops_register(gr_nh_type_t type, const struct nexthop_ops *); 70 | 71 | #endif 72 | -------------------------------------------------------------------------------- /modules/infra/control/gr_port.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2023 Robin Jarry 3 | 4 | #ifndef _GR_INFRA_PORT 5 | #define _GR_INFRA_PORT 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | 17 | enum { 18 | MAC_FILTER_F_UNSUPP = GR_BIT8(0), 19 | MAC_FILTER_F_NOSPC = GR_BIT8(1), 20 | MAC_FILTER_F_ALL = GR_BIT8(2), 21 | }; 22 | 23 | struct mac_filter { 24 | uint8_t flags; 25 | uint8_t count; 26 | uint8_t hw_limit; 27 | uint16_t refcnt[RTE_ETH_NUM_RECEIVE_MAC_ADDR]; 28 | struct rte_ether_addr mac[RTE_ETH_NUM_RECEIVE_MAC_ADDR]; 29 | }; 30 | 31 | struct __rte_aligned(alignof(void *)) iface_info_port { 32 | BASE(__gr_iface_info_port_base); 33 | 34 | uint16_t port_id; 35 | bool started; 36 | struct rte_mempool *pool; 37 | char *devargs; 38 | uint32_t pool_size; 39 | struct mac_filter ucast_filter; 40 | struct mac_filter mcast_filter; 41 | }; 42 | 43 | uint32_t port_get_rxq_buffer_us(uint16_t port_id, uint16_t rxq_id); 44 | const struct iface *port_get_iface(uint16_t port_id); 45 | 46 | #endif 47 | -------------------------------------------------------------------------------- /modules/infra/control/gr_vlan.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2024 Robin Jarry 3 | 4 | #ifndef _GR_INFRA_VLAN_PRIV 5 | #define _GR_INFRA_VLAN_PRIV 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | 15 | struct __rte_aligned(alignof(void *)) iface_info_vlan { 16 | BASE(gr_iface_info_vlan); 17 | }; 18 | 19 | struct iface *vlan_get_iface(uint16_t port_id, uint16_t vlan_id); 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /modules/infra/control/gr_worker.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2023 Robin Jarry 3 | 4 | #ifndef _GR_INFRA_WORKER 5 | #define _GR_INFRA_WORKER 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | struct queue_map { 21 | uint16_t port_id; 22 | uint16_t queue_id; 23 | bool enabled; 24 | }; 25 | 26 | struct node_stats { 27 | rte_node_t node_id; 28 | uint64_t objs; 29 | uint64_t calls; 30 | uint64_t cycles; 31 | }; 32 | 33 | struct worker_stats { 34 | uint64_t total_cycles; 35 | uint64_t busy_cycles; 36 | uint64_t sleep_cycles; 37 | uint64_t n_sleeps; 38 | size_t n_stats; 39 | struct node_stats stats[/* n_stats */]; 40 | }; 41 | 42 | struct worker { 43 | atomic_bool shutdown; // dataplane: ro, ctlplane: wo 44 | atomic_uint next_config; // dataplane: ro, ctlplane: rw 45 | atomic_uint cur_config; // dataplane: wo, ctlplane: ro 46 | // synced with thread_fence 47 | struct rte_graph *graph[2]; // dataplane: ro, ctlplane: rw 48 | atomic_uint max_sleep_us; // dataplane: ro, ctlplane: rw 49 | 50 | atomic_bool stats_reset; // dataplane: rw, ctlplane: rw 51 | // dataplane: wo, ctlplane: ro, may be NULL 52 | _Atomic(const struct worker_stats *) stats; 53 | 54 | // shared between control & dataplane 55 | unsigned cpu_id; 56 | unsigned lcore_id; 57 | pid_t tid; 58 | 59 | pthread_mutex_t lock; 60 | pthread_cond_t ready; 61 | 62 | // private for control plane only 63 | pthread_t thread; 64 | struct queue_map *rxqs; 65 | struct queue_map *txqs; 66 | STAILQ_ENTRY(worker) next; 67 | } __rte_cache_aligned; 68 | 69 | STAILQ_HEAD(workers, worker); 70 | extern struct workers workers; 71 | 72 | int worker_rxq_assign(uint16_t port_id, uint16_t rxq_id, uint16_t cpu_id); 73 | int worker_queue_distribute(const cpu_set_t *affinity, struct iface_info_port **ports); 74 | void worker_wait_ready(struct worker *); 75 | void worker_signal_ready(struct worker *); 76 | 77 | #endif 78 | -------------------------------------------------------------------------------- /modules/infra/control/graph_priv.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2024 Robin Jarry 3 | 4 | #ifndef _GR_CONTROL_GRAPH 5 | #define _GR_CONTROL_GRAPH 6 | 7 | #include 8 | 9 | int worker_graph_reload(struct worker *); 10 | int worker_graph_reload_all(void); 11 | void worker_graph_free(struct worker *); 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /modules/infra/control/meson.build: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Copyright (c) 2023 Robin Jarry 3 | 4 | src += files( 5 | 'control_output.c', 6 | 'iface.c', 7 | 'loopback.c', 8 | 'mempool.c', 9 | 'nexthop.c', 10 | 'port.c', 11 | 'worker.c', 12 | 'graph.c', 13 | 'vlan.c', 14 | 'vrf.c', 15 | ) 16 | inc += include_directories('.') 17 | 18 | tests += [ 19 | { 20 | 'sources': files('worker_test.c', 'port.c', 'worker.c'), 21 | 'link_args': [ 22 | '-Wl,--wrap=numa_available', 23 | '-Wl,--wrap=numa_node_of_cpu', 24 | '-Wl,--wrap=pthread_cancel', 25 | '-Wl,--wrap=pthread_cond_wait', 26 | '-Wl,--wrap=pthread_cond_timedwait', 27 | '-Wl,--wrap=pthread_create', 28 | '-Wl,--wrap=pthread_join', 29 | '-Wl,--wrap=rte_dev_name', 30 | '-Wl,--wrap=rte_eth_dev_configure', 31 | '-Wl,--wrap=rte_eth_dev_get_mtu', 32 | '-Wl,--wrap=rte_eth_dev_info_get', 33 | '-Wl,--wrap=rte_eth_dev_socket_id', 34 | '-Wl,--wrap=rte_eth_dev_start', 35 | '-Wl,--wrap=rte_eth_dev_stop', 36 | '-Wl,--wrap=rte_eth_macaddr_get', 37 | '-Wl,--wrap=rte_eth_rx_queue_setup', 38 | '-Wl,--wrap=rte_eth_tx_queue_setup', 39 | '-Wl,--wrap=rte_free', 40 | '-Wl,--wrap=rte_mempool_free', 41 | '-Wl,--wrap=rte_pktmbuf_pool_create', 42 | '-Wl,--wrap=rte_zmalloc', 43 | ], 44 | } 45 | ] 46 | -------------------------------------------------------------------------------- /modules/infra/control/vrf.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2024 Christophe Fontaine 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | struct vrf_info { 13 | int ref_count; 14 | struct iface *iface; 15 | }; 16 | 17 | // we have the same number of VRFs for IP4 and IP6 18 | static struct vrf_info vrfs[MAX_VRFS]; 19 | 20 | struct iface *get_vrf_iface(uint16_t vrf_id) { 21 | return vrfs[vrf_id].iface; 22 | } 23 | 24 | static void iface_event_vrf(uint32_t event, const void *obj) { 25 | const struct iface *iface = obj; 26 | int ifaces_per_vrf[ARRAY_DIM(vrfs)] = {0}; 27 | 28 | if (iface->type == GR_IFACE_TYPE_LOOPBACK) 29 | return; 30 | 31 | switch (event) { 32 | case GR_EVENT_IFACE_POST_ADD: 33 | if (++vrfs[iface->vrf_id].ref_count == 1) 34 | vrfs[iface->vrf_id].iface = iface_loopback_create(iface->vrf_id); 35 | break; 36 | case GR_EVENT_IFACE_PRE_REMOVE: 37 | if (--vrfs[iface->vrf_id].ref_count == 0) { 38 | iface_loopback_delete(iface->vrf_id); 39 | vrfs[iface->vrf_id].iface = NULL; 40 | } 41 | break; 42 | case GR_EVENT_IFACE_POST_RECONFIG: 43 | iface = NULL; 44 | while ((iface = iface_next(GR_IFACE_TYPE_UNDEF, iface)) != NULL) { 45 | if (iface->type == GR_IFACE_TYPE_LOOPBACK) 46 | continue; 47 | if (iface->mode == GR_IFACE_MODE_L3) 48 | ifaces_per_vrf[iface->vrf_id]++; 49 | } 50 | for (unsigned i = 0; i < ARRAY_DIM(vrfs); i++) { 51 | if (vrfs[i].ref_count > ifaces_per_vrf[i]) { 52 | vrfs[i].ref_count = ifaces_per_vrf[i]; 53 | if (vrfs[i].ref_count == 0) { 54 | iface_loopback_delete(i); 55 | vrfs[i].iface = NULL; 56 | } 57 | } else if (vrfs[i].ref_count < ifaces_per_vrf[i]) { 58 | vrfs[i].ref_count = ifaces_per_vrf[i]; 59 | if (vrfs[i].ref_count == 1) 60 | vrfs[i].iface = iface_loopback_create(i); 61 | } 62 | } 63 | break; 64 | } 65 | } 66 | 67 | static struct gr_event_subscription iface_event_vrf_sub = { 68 | .callback = iface_event_vrf, 69 | .ev_count = 3, 70 | .ev_types = { 71 | GR_EVENT_IFACE_POST_ADD, 72 | GR_EVENT_IFACE_PRE_REMOVE, 73 | GR_EVENT_IFACE_POST_RECONFIG, 74 | }, 75 | }; 76 | 77 | RTE_INIT(vrf_constructor) { 78 | gr_event_subscribe(&iface_event_vrf_sub); 79 | } 80 | -------------------------------------------------------------------------------- /modules/infra/control/worker_priv.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2023 Robin Jarry 3 | 4 | #ifndef _GR_CONTROL_WORKER 5 | #define _GR_CONTROL_WORKER 6 | 7 | #include 8 | #include 9 | 10 | int port_unplug(uint16_t port_id); 11 | int port_plug(uint16_t port_id); 12 | int port_configure(struct iface_info_port *, uint16_t n_txq_min); 13 | 14 | unsigned worker_count(void); 15 | int worker_create(unsigned cpu_id); 16 | struct worker *worker_find(unsigned cpu_id); 17 | int worker_destroy(unsigned cpu_id); 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /modules/infra/control/xconnect.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2025 Christophe Fontaine 3 | 4 | #include "gr_l2.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | static struct api_out l2_mode_set(const void *request, void ** /* response */) { 12 | const struct gr_l2_iface_mode_req *req = request; 13 | struct iface *iface; 14 | 15 | iface = iface_from_id(req->iface_id); 16 | if (iface == NULL) 17 | return api_out(errno, 0); 18 | 19 | // Clean all L3 related info 20 | if (req->mode != GR_IFACE_MODE_L3) 21 | gr_event_push(GR_EVENT_IFACE_STATUS_DOWN, iface); 22 | 23 | iface->mode = req->mode; 24 | iface->bridge_domain = req->domain_id; 25 | 26 | if (req->mode == GR_IFACE_MODE_L3) 27 | gr_event_push(GR_EVENT_IFACE_STATUS_UP, iface); 28 | 29 | return api_out(0, 0); 30 | } 31 | 32 | static struct gr_api_handler l2_mode_set_handler = { 33 | .name = "l2 xconnect set", 34 | .request_type = GR_L2_MODE_SET, 35 | .callback = l2_mode_set, 36 | }; 37 | 38 | RTE_INIT(l2_constructor) { 39 | gr_register_api_handler(&l2_mode_set_handler); 40 | } 41 | -------------------------------------------------------------------------------- /modules/infra/datapath/control_output.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2024 Christophe Fontaine 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | #define ERROR 0 13 | 14 | static uint16_t control_output_process( 15 | struct rte_graph *graph, 16 | struct rte_node *node, 17 | void **objs, 18 | uint16_t n_objs 19 | ) { 20 | unsigned sent = 0; 21 | 22 | for (unsigned i = 0; i < n_objs; i++) { 23 | if (control_output_enqueue(objs[i]) < 0) 24 | rte_node_enqueue_x1(graph, node, ERROR, objs[i]); 25 | else { 26 | sent++; 27 | if (gr_mbuf_is_traced(objs[i])) { 28 | // FIXME racy: we are operating on mbufs already enqueued in ring 29 | gr_mbuf_trace_add(objs[i], node, 0); 30 | gr_mbuf_trace_finish(objs[i]); 31 | } 32 | } 33 | } 34 | if (sent > 0) 35 | control_output_done(); 36 | 37 | return n_objs; 38 | } 39 | 40 | static struct rte_node_register control_output_node = { 41 | .name = "control_output", 42 | .process = control_output_process, 43 | .nb_edges = 1, 44 | .next_nodes = { 45 | [ERROR] = "control_output_error", 46 | }, 47 | }; 48 | 49 | static struct gr_node_info info = { 50 | .node = &control_output_node, 51 | }; 52 | 53 | GR_NODE_REGISTER(info); 54 | 55 | GR_DROP_REGISTER(control_output_error); 56 | -------------------------------------------------------------------------------- /modules/infra/datapath/drop.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2024 Robin Jarry 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | uint16_t drop_packets(struct rte_graph *, struct rte_node *node, void **objs, uint16_t nb_objs) { 13 | if (unlikely(gr_config.log_packets)) 14 | LOG(NOTICE, "[%s] %u packets", node->name, nb_objs); 15 | 16 | for (int i = 0; i < nb_objs; i++) { 17 | struct rte_mbuf *mbuf = objs[i]; 18 | if (gr_mbuf_is_traced(mbuf)) { 19 | gr_mbuf_trace_add(mbuf, node, 0); 20 | gr_mbuf_trace_finish(mbuf); 21 | } 22 | } 23 | rte_pktmbuf_free_bulk((struct rte_mbuf **)objs, nb_objs); 24 | 25 | return nb_objs; 26 | } 27 | 28 | int drop_format(char *buf, size_t len, const void * /*data*/, size_t /*data_len*/) { 29 | return snprintf(buf, len, "drop"); 30 | } 31 | 32 | // Global drop counters, used by multiple nodes 33 | GR_DROP_REGISTER(error_no_headroom); 34 | -------------------------------------------------------------------------------- /modules/infra/datapath/eth_output.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2024 Robin Jarry 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | #include 16 | 17 | enum { 18 | TX = 0, 19 | INVAL, 20 | NO_HEADROOM, 21 | NB_EDGES, 22 | }; 23 | 24 | static uint16_t 25 | eth_output_process(struct rte_graph *graph, struct rte_node *node, void **objs, uint16_t nb_objs) { 26 | const struct rte_ether_addr *src_mac; 27 | const struct iface_info_port *port; 28 | struct eth_output_mbuf_data *priv; 29 | struct iface_info_vlan *sub; 30 | struct rte_vlan_hdr *vlan; 31 | struct rte_ether_hdr *eth; 32 | struct rte_mbuf *mbuf; 33 | 34 | for (uint16_t i = 0; i < nb_objs; i++) { 35 | mbuf = objs[i]; 36 | priv = eth_output_mbuf_data(mbuf); 37 | vlan = NULL; 38 | 39 | switch (priv->iface->type) { 40 | case GR_IFACE_TYPE_VLAN: 41 | sub = (struct iface_info_vlan *)priv->iface->info; 42 | vlan = (struct rte_vlan_hdr *)rte_pktmbuf_prepend(mbuf, sizeof(*vlan)); 43 | if (unlikely(vlan == NULL)) { 44 | rte_node_enqueue_x1(graph, node, NO_HEADROOM, mbuf); 45 | continue; 46 | } 47 | vlan->vlan_tci = rte_cpu_to_be_16(sub->vlan_id); 48 | vlan->eth_proto = priv->ether_type; 49 | priv->ether_type = RTE_BE16(RTE_ETHER_TYPE_VLAN); 50 | priv->iface = iface_from_id(sub->parent_id); 51 | src_mac = &sub->mac; 52 | port = (const struct iface_info_port *)priv->iface->info; 53 | break; 54 | case GR_IFACE_TYPE_PORT: 55 | port = (const struct iface_info_port *)priv->iface->info; 56 | src_mac = &port->mac; 57 | break; 58 | default: 59 | rte_node_enqueue_x1(graph, node, INVAL, mbuf); 60 | continue; 61 | } 62 | 63 | eth = (struct rte_ether_hdr *)rte_pktmbuf_prepend(mbuf, sizeof(*eth)); 64 | if (unlikely(eth == NULL)) { 65 | rte_node_enqueue_x1(graph, node, NO_HEADROOM, mbuf); 66 | continue; 67 | } 68 | eth->dst_addr = priv->dst; 69 | eth->src_addr = *src_mac; 70 | eth->ether_type = priv->ether_type; 71 | mbuf->port = port->port_id; 72 | 73 | if (gr_mbuf_is_traced(mbuf)) { 74 | struct eth_trace_data *t = gr_mbuf_trace_add(mbuf, node, sizeof(*t)); 75 | t->eth.dst_addr = eth->dst_addr; 76 | t->eth.src_addr = eth->src_addr; 77 | t->eth.ether_type = vlan ? vlan->eth_proto : eth->ether_type; 78 | t->vlan_id = rte_be_to_cpu_16(vlan ? vlan->vlan_tci : 0); 79 | t->iface_id = priv->iface->id; 80 | } 81 | 82 | rte_node_enqueue_x1(graph, node, TX, mbuf); 83 | } 84 | 85 | return nb_objs; 86 | } 87 | 88 | static struct rte_node_register node = { 89 | .name = "eth_output", 90 | 91 | .process = eth_output_process, 92 | 93 | .nb_edges = NB_EDGES, 94 | .next_nodes = { 95 | [TX] = "port_tx", 96 | [INVAL] = "eth_output_inval", 97 | [NO_HEADROOM] = "error_no_headroom", 98 | }, 99 | }; 100 | 101 | static struct gr_node_info info = { 102 | .node = &node, 103 | .trace_format = eth_trace_format, 104 | }; 105 | 106 | GR_NODE_REGISTER(info); 107 | 108 | GR_DROP_REGISTER(eth_output_inval); 109 | -------------------------------------------------------------------------------- /modules/infra/datapath/gr_control_input.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2024 Christophe Fontaine 3 | 4 | #ifndef _GR_CONTROL_INPUT_PATH 5 | #define _GR_CONTROL_INPUT_PATH 6 | 7 | #include 8 | 9 | GR_MBUF_PRIV_DATA_TYPE(control_input_mbuf_data, { void *data; }); 10 | 11 | typedef uint8_t control_input_t; 12 | 13 | control_input_t gr_control_input_register_handler(const char *node_name, bool data_is_mbuf); 14 | 15 | int post_to_stack(control_input_t type, void *data); 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /modules/infra/datapath/gr_control_output.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2024 Christophe Fontaine 3 | 4 | #ifndef _GR_CONTROL_OUTPUT_PATH 5 | #define _GR_CONTROL_OUTPUT_PATH 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | // Callback definition when a packet is sent from the data plane to the control plane. 13 | // It is up to the function to free the received mbuf. 14 | // 15 | // @param struct rte_mbuf * 16 | // Packet with the data offset set to the OSI layer of the originating node. 17 | typedef void (*control_output_cb_t)(struct rte_mbuf *); 18 | 19 | GR_MBUF_PRIV_DATA_TYPE(control_output_mbuf_data, { 20 | control_output_cb_t callback; 21 | clock_t timestamp; 22 | uint8_t cb_data[GR_MBUF_PRIV_MAX_SIZE - 6 * sizeof(size_t)]; 23 | }); 24 | 25 | // Enqueue a packet from data plane to a control plane ring. 26 | // 27 | // NB: control_output_done() must be called explicitly to wake up the control plane event loop. 28 | int control_output_enqueue(struct rte_mbuf *m); 29 | 30 | // Wake up the control plane event loop so that it processes the pending packets. 31 | void control_output_done(void); 32 | 33 | // Change the thread affinity of the control output thread. 34 | int control_output_set_affinity(size_t set_size, const cpu_set_t *affinity); 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /modules/infra/datapath/gr_datapath.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2023 Robin Jarry 3 | 4 | #ifndef _GR_INFRA_DATAPATH 5 | #define _GR_INFRA_DATAPATH 6 | 7 | void *gr_datapath_loop(void *priv); 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /modules/infra/datapath/gr_eth.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2024 Robin Jarry 3 | 4 | #ifndef _GR_INFRA_ETH 5 | #define _GR_INFRA_ETH 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | typedef enum { 15 | ETH_DOMAIN_UNKNOWN = 0, 16 | ETH_DOMAIN_LOOPBACK, // packet comes from a local loopback interface 17 | ETH_DOMAIN_LOCAL, // destination is the input interface mac 18 | ETH_DOMAIN_BROADCAST, // destination is ff:ff:ff:ff:ff:ff 19 | ETH_DOMAIN_MULTICAST, // destination is a multicast ethernet address 20 | ETH_DOMAIN_OTHER, // destination is *not* the input interface mac 21 | } eth_domain_t; 22 | 23 | GR_MBUF_PRIV_DATA_TYPE(eth_input_mbuf_data, { eth_domain_t domain; }) 24 | 25 | GR_MBUF_PRIV_DATA_TYPE(eth_output_mbuf_data, { 26 | struct rte_ether_addr dst; 27 | rte_be16_t ether_type; 28 | }); 29 | 30 | void gr_eth_input_add_type(rte_be16_t eth_type, const char *node_name); 31 | 32 | struct eth_trace_data { 33 | struct rte_ether_hdr eth; 34 | uint16_t vlan_id; 35 | uint16_t iface_id; 36 | }; 37 | 38 | int eth_trace_format(char *buf, size_t len, const void *data, size_t /*data_len*/); 39 | 40 | #endif 41 | -------------------------------------------------------------------------------- /modules/infra/datapath/gr_mbuf.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2024 Robin Jarry 3 | 4 | #ifndef _GR_MBUF 5 | #define _GR_MBUF 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | #define GR_TRACE_ITEM_MAX_LEN 256 15 | 16 | struct gr_trace_item { 17 | STAILQ_ENTRY(gr_trace_item) next; 18 | struct timespec ts; 19 | unsigned cpu_id; 20 | rte_node_t node_id; 21 | uint8_t len; 22 | uint8_t data[GR_TRACE_ITEM_MAX_LEN]; 23 | }; 24 | 25 | STAILQ_HEAD(gr_trace_head, gr_trace_item); 26 | 27 | #define GR_MBUF_PRIV_MAX_SIZE RTE_CACHE_LINE_MIN_SIZE * 2 28 | 29 | #define GR_MBUF_PRIV_DATA_TYPE(type_name, fields) \ 30 | struct type_name { \ 31 | const struct iface *iface; \ 32 | struct fields; \ 33 | }; \ 34 | struct __##type_name { \ 35 | struct gr_trace_head traces; \ 36 | struct type_name data; \ 37 | }; \ 38 | static inline struct type_name *type_name(struct rte_mbuf *m) { \ 39 | static_assert(sizeof(struct __##type_name) <= GR_MBUF_PRIV_MAX_SIZE); \ 40 | struct __##type_name *priv = rte_mbuf_to_priv(m); \ 41 | return &priv->data; \ 42 | } 43 | 44 | GR_MBUF_PRIV_DATA_TYPE(mbuf_data, {}); 45 | GR_MBUF_PRIV_DATA_TYPE(queue_mbuf_data, { struct rte_mbuf *next; }); 46 | 47 | // Get the head of trace items from an mbuf. 48 | static inline struct gr_trace_head *gr_mbuf_traces(struct rte_mbuf *m) { 49 | return rte_mbuf_to_priv(m); 50 | } 51 | 52 | // Return true the mbuf already contains trace items. 53 | static inline bool gr_mbuf_is_traced(struct rte_mbuf *m) { 54 | return !STAILQ_EMPTY(gr_mbuf_traces(m)); 55 | } 56 | 57 | // Append a trace item to an mbuf. 58 | // 59 | // If the mbuf didn't contain any traces, store it as the first one and record 60 | // the current time into it. 61 | // 62 | // This cannot fail. If there are no free trace items available, the trace 63 | // buffer will be emptied starting from the oldest until one can be returned. 64 | // 65 | // Returns a pointer to a gr_trace_item.data buffer. 66 | void *gr_mbuf_trace_add(struct rte_mbuf *m, struct rte_node *node, size_t data_len); 67 | 68 | // Detach the trace items from an mbuf and store them in the trace buffer. 69 | void gr_mbuf_trace_finish(struct rte_mbuf *m); 70 | 71 | #endif 72 | -------------------------------------------------------------------------------- /modules/infra/datapath/gr_rxtx.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2024 Robin Jarry 3 | 4 | #ifndef _GR_INFRA_RXTX 5 | #define _GR_INFRA_RXTX 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | struct rx_port_queue { 13 | uint16_t port_id; 14 | uint16_t rxq_id; 15 | }; 16 | 17 | struct rx_node_queues { 18 | uint16_t n_queues; 19 | struct rx_port_queue queues[/* n_queues */]; 20 | }; 21 | 22 | struct tx_node_queues { 23 | uint16_t txq_ids[RTE_MAX_ETHPORTS]; 24 | }; 25 | 26 | struct rxtx_trace_data { 27 | uint16_t port_id; 28 | uint16_t queue_id; 29 | }; 30 | 31 | int rxtx_trace_format(char *buf, size_t len, const void *data, size_t /*data_len*/); 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /modules/infra/datapath/gr_trace.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2024 Christophe Fontaine 3 | // Copyright (c) 2024 Robin Jarry 4 | 5 | #ifndef _GR_INFRA_PACKET_TRACE 6 | #define _GR_INFRA_PACKET_TRACE 7 | 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | // Write a log message with detailed packet information. 17 | void trace_log_packet(const struct rte_mbuf *m, const char *node, const char *iface); 18 | 19 | // Callback associated with each node that will be invoked by gr_trace_dump 20 | // to format each individual trace items. 21 | typedef int (*gr_trace_format_cb_t)(char *buf, size_t buf_len, const void *data, size_t data_len); 22 | 23 | // Format the buffered trace items, emptying the trace ring of max_packets. 24 | // Set the number of written bytes to n_bytes and the number of dumped packets to n_packets. 25 | // Return 0 on success or a negative value on error. 26 | int gr_trace_dump( 27 | char *buf, 28 | size_t buf_len, 29 | uint16_t max_packets, 30 | uint32_t *n_bytes, 31 | uint16_t *n_packets 32 | ); 33 | 34 | // Empty the trace buffer. 35 | void gr_trace_clear(void); 36 | 37 | // Return true if trace is enabled for all interfaces. 38 | bool gr_trace_all_enabled(void); 39 | 40 | int eth_type_format(char *buf, size_t len, rte_be16_t type); 41 | 42 | int trace_arp_format(char *buf, size_t len, const struct rte_arp_hdr *, size_t data_len); 43 | 44 | int trace_ip_format(char *buf, size_t len, const struct rte_ipv4_hdr *, size_t data_len); 45 | 46 | int trace_ip6_format(char *buf, size_t len, const struct rte_ipv6_hdr *, size_t data_len); 47 | 48 | int trace_icmp_format(char *buf, size_t len, const struct rte_icmp_hdr *, size_t data_len); 49 | 50 | int trace_icmp6_format(char *buf, size_t len, const struct icmp6 *, size_t data_len); 51 | 52 | #endif 53 | -------------------------------------------------------------------------------- /modules/infra/datapath/l1_xconnect.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2025 Christophe Fontaine 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | enum edges { 15 | TX = 0, 16 | NO_PORT, 17 | EDGE_COUNT 18 | }; 19 | 20 | static uint16_t 21 | l1_xconnect_process(struct rte_graph *graph, struct rte_node *node, void **objs, uint16_t nb_objs) { 22 | const struct iface_info_port *port; 23 | const struct iface *iface, *peer; 24 | struct rte_mbuf *mbuf; 25 | rte_edge_t edge; 26 | 27 | for (uint16_t i = 0; i < nb_objs; i++) { 28 | mbuf = objs[i]; 29 | iface = mbuf_data(mbuf)->iface; 30 | peer = iface_from_id(iface->domain_id); 31 | if (peer->type == GR_IFACE_TYPE_PORT) { 32 | port = (const struct iface_info_port *)peer->info; 33 | mbuf->port = port->port_id; 34 | edge = TX; 35 | } else { 36 | edge = NO_PORT; 37 | } 38 | 39 | if (gr_mbuf_is_traced(mbuf)) { 40 | gr_mbuf_trace_add(mbuf, node, 0); 41 | } 42 | rte_node_enqueue_x1(graph, node, edge, mbuf); 43 | } 44 | 45 | return nb_objs; 46 | } 47 | 48 | static struct rte_node_register xconnect_node = { 49 | .name = "l1_xconnect", 50 | .process = l1_xconnect_process, 51 | .nb_edges = EDGE_COUNT, 52 | .next_nodes = { 53 | [TX] = "port_tx", 54 | [NO_PORT] = "xconnect_no_tx_port", 55 | }, 56 | }; 57 | 58 | static void l1_xconnect_register(void) { 59 | register_interface_mode(GR_IFACE_MODE_L1_XC, "l1_xconnect"); 60 | } 61 | 62 | static struct gr_node_info info = { 63 | .node = &xconnect_node, 64 | .register_callback = l1_xconnect_register, 65 | }; 66 | 67 | GR_NODE_REGISTER(info); 68 | GR_DROP_REGISTER(xconnect_no_tx_port); 69 | -------------------------------------------------------------------------------- /modules/infra/datapath/loop_input.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2024 Christophe Fontaine 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | static control_input_t control_to_loopback_input; 15 | 16 | control_input_t loopback_get_control_id(void) { 17 | return control_to_loopback_input; 18 | } 19 | 20 | enum { 21 | UNKNOWN_PROTO = 0, 22 | EDGE_COUNT, 23 | }; 24 | 25 | static rte_edge_t l3_edges[1 << 16] = {UNKNOWN_PROTO}; 26 | 27 | void loopback_input_add_type(rte_be16_t eth_type, const char *next_node) { 28 | LOG(DEBUG, "loopback_input: type=0x%04x -> %s", rte_be_to_cpu_16(eth_type), next_node); 29 | if (l3_edges[eth_type] != UNKNOWN_PROTO) 30 | ABORT("next node already registered for ether type=0x%04x", 31 | rte_be_to_cpu_16(eth_type)); 32 | l3_edges[eth_type] = gr_node_attach_parent("loopback_input", next_node); 33 | } 34 | 35 | static uint16_t loopback_input_process( 36 | struct rte_graph *graph, 37 | struct rte_node *node, 38 | void **objs, 39 | uint16_t nb_objs 40 | ) { 41 | struct rte_mbuf *mbuf; 42 | rte_be16_t eth_type; 43 | rte_edge_t edge; 44 | 45 | for (uint16_t i = 0; i < nb_objs; i++) { 46 | mbuf = objs[i]; 47 | 48 | if (gr_mbuf_is_traced(mbuf) 49 | || mbuf_data(mbuf)->iface->flags & GR_IFACE_F_PACKET_TRACE) { 50 | gr_mbuf_trace_add(mbuf, node, 0); 51 | } 52 | 53 | eth_type = rte_pktmbuf_mtod(mbuf, struct tun_pi *)->proto; 54 | rte_pktmbuf_adj(mbuf, sizeof(struct tun_pi)); 55 | edge = l3_edges[eth_type]; 56 | rte_node_enqueue_x1(graph, node, edge, mbuf); 57 | } 58 | return nb_objs; 59 | } 60 | 61 | static struct rte_node_register loopback_input_node = { 62 | .name = "loopback_input", 63 | .process = loopback_input_process, 64 | .nb_edges = EDGE_COUNT, 65 | .next_nodes = { 66 | [UNKNOWN_PROTO] = "loopback_unknown_proto", 67 | }, 68 | }; 69 | 70 | static void loopback_input_register(void) { 71 | control_to_loopback_input = gr_control_input_register_handler("loopback_input", true); 72 | } 73 | 74 | static struct gr_node_info info = { 75 | .node = &loopback_input_node, 76 | .register_callback = loopback_input_register, 77 | }; 78 | 79 | GR_NODE_REGISTER(info); 80 | GR_DROP_REGISTER(loopback_unknown_proto); 81 | -------------------------------------------------------------------------------- /modules/infra/datapath/loop_output.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2024 Christophe Fontaine 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | enum { 13 | CONTROL_OUTPUT, 14 | EDGE_COUNT, 15 | }; 16 | 17 | static uint16_t loopback_output_process( 18 | struct rte_graph *graph, 19 | struct rte_node *node, 20 | void **objs, 21 | uint16_t nb_objs 22 | ) { 23 | struct control_output_mbuf_data *co; 24 | struct rte_mbuf *mbuf; 25 | 26 | for (uint16_t i = 0; i < nb_objs; i++) { 27 | mbuf = objs[i]; 28 | co = control_output_mbuf_data(mbuf); 29 | co->callback = loopback_tx; 30 | rte_node_enqueue_x1(graph, node, CONTROL_OUTPUT, mbuf); 31 | } 32 | return nb_objs; 33 | } 34 | 35 | static struct rte_node_register loopback_output_node = { 36 | .name = "loopback_output", 37 | .process = loopback_output_process, 38 | .nb_edges = EDGE_COUNT, 39 | .next_nodes = { 40 | [CONTROL_OUTPUT] = "control_output", 41 | }, 42 | }; 43 | 44 | static void loopback_output_register(void) { 45 | ip_output_register_interface_type(GR_IFACE_TYPE_LOOPBACK, "loopback_output"); 46 | ip6_output_register_interface_type(GR_IFACE_TYPE_LOOPBACK, "loopback_output"); 47 | } 48 | 49 | static struct gr_node_info info = { 50 | .node = &loopback_output_node, 51 | .register_callback = loopback_output_register, 52 | }; 53 | 54 | GR_NODE_REGISTER(info); 55 | -------------------------------------------------------------------------------- /modules/infra/datapath/meson.build: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Copyright (c) 2023 Robin Jarry 3 | 4 | src += files( 5 | 'control_input.c', 6 | 'control_output.c', 7 | 'drop.c', 8 | 'eth_input.c', 9 | 'eth_output.c', 10 | 'l1_xconnect.c', 11 | 'loop_input.c', 12 | 'loop_output.c', 13 | 'main_loop.c', 14 | 'rx.c', 15 | 'trace.c', 16 | 'tx.c', 17 | ) 18 | inc += include_directories('.') 19 | -------------------------------------------------------------------------------- /modules/infra/meson.build: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Copyright (c) 2023 Robin Jarry 3 | 4 | subdir('api') 5 | subdir('cli') 6 | subdir('control') 7 | subdir('datapath') 8 | -------------------------------------------------------------------------------- /modules/ip/api/meson.build: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Copyright (c) 2024 Robin Jarry 3 | 4 | api_headers += files('gr_ip4.h') 5 | api_inc += include_directories('.') 6 | -------------------------------------------------------------------------------- /modules/ip/cli/ip.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2024 Robin Jarry 3 | 4 | #ifndef _GR_CLI_IP 5 | #define _GR_CLI_IP 6 | 7 | #include 8 | 9 | #define IP_ADD_CTX(root) CLI_CONTEXT(root, CTX_ADD, CTX_ARG("ip", "Create IPv4 stack elements.")) 10 | #define IP_DEL_CTX(root) CLI_CONTEXT(root, CTX_DEL, CTX_ARG("ip", "Delete IPv4 stack elements.")) 11 | #define IP_SHOW_CTX(root) CLI_CONTEXT(root, CTX_SHOW, CTX_ARG("ip", "Show IPv4 stack details.")) 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /modules/ip/cli/meson.build: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Copyright (c) 2024 Robin Jarry 3 | 4 | cli_src += files( 5 | 'address.c', 6 | 'nexthop.c', 7 | 'route.c', 8 | 'icmp.c', 9 | ) 10 | -------------------------------------------------------------------------------- /modules/ip/control/gr_ip4_control.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2024 Robin Jarry 3 | 4 | #ifndef _GR_IP4_CONTROL 5 | #define _GR_IP4_CONTROL 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | 18 | static inline struct nexthop *nh4_new(uint16_t vrf_id, uint16_t iface_id, ip4_addr_t ip) { 19 | return nexthop_new(GR_NH_IPV4, vrf_id, iface_id, &ip); 20 | } 21 | 22 | static inline struct nexthop *nh4_lookup(uint16_t vrf_id, ip4_addr_t ip) { 23 | // XXX: should we scope ip4 nh lookup based on rfc3927 ? 24 | return nexthop_lookup(GR_NH_IPV4, vrf_id, GR_IFACE_ID_UNDEF, &ip); 25 | } 26 | 27 | void nh4_unreachable_cb(struct rte_mbuf *m); 28 | void arp_probe_input_cb(struct rte_mbuf *m); 29 | 30 | struct nexthop *rib4_lookup(uint16_t vrf_id, ip4_addr_t ip); 31 | struct nexthop *rib4_lookup_exact(uint16_t vrf_id, ip4_addr_t ip, uint8_t prefixlen); 32 | int rib4_insert( 33 | uint16_t vrf_id, 34 | ip4_addr_t ip, 35 | uint8_t prefixlen, 36 | gr_rt_origin_t origin, 37 | struct nexthop *nh 38 | ); 39 | int rib4_delete(uint16_t vrf_id, ip4_addr_t ip, uint8_t prefixlen); 40 | void rib4_cleanup(struct nexthop *); 41 | 42 | // get the default address for a given interface 43 | struct nexthop *addr4_get_preferred(uint16_t iface_id, ip4_addr_t dst); 44 | // get all addresses for a given interface 45 | struct hoplist *addr4_get_all(uint16_t iface_id); 46 | 47 | #endif 48 | -------------------------------------------------------------------------------- /modules/ip/control/meson.build: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Copyright (c) 2024 Robin Jarry 3 | 4 | src += files( 5 | 'address.c', 6 | 'icmp.c', 7 | 'nexthop.c', 8 | 'route.c', 9 | ) 10 | inc += include_directories('.') 11 | -------------------------------------------------------------------------------- /modules/ip/datapath/arp_input.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2024 Robin Jarry 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | enum { 17 | OP_REQUEST = 0, 18 | OP_REPLY, 19 | OP_UNSUPP, 20 | PROTO_UNSUPP, 21 | EDGE_COUNT, 22 | }; 23 | 24 | static uint16_t 25 | arp_input_process(struct rte_graph *graph, struct rte_node *node, void **objs, uint16_t nb_objs) { 26 | struct rte_arp_hdr *arp, *t; 27 | struct rte_mbuf *mbuf; 28 | rte_edge_t edge; 29 | 30 | for (uint16_t i = 0; i < nb_objs; i++) { 31 | mbuf = objs[i]; 32 | 33 | // ARP protocol sanity checks. 34 | arp = rte_pktmbuf_mtod(mbuf, struct rte_arp_hdr *); 35 | if (arp->arp_hardware != RTE_BE16(RTE_ARP_HRD_ETHER)) { 36 | edge = PROTO_UNSUPP; 37 | goto next; 38 | } 39 | if (arp->arp_protocol != RTE_BE16(RTE_ETHER_TYPE_IPV4)) { 40 | edge = PROTO_UNSUPP; 41 | goto next; 42 | } 43 | switch (arp->arp_opcode) { 44 | case RTE_BE16(RTE_ARP_OP_REQUEST): 45 | edge = OP_REQUEST; 46 | break; 47 | case RTE_BE16(RTE_ARP_OP_REPLY): 48 | edge = OP_REPLY; 49 | break; 50 | default: 51 | edge = OP_UNSUPP; 52 | break; 53 | } 54 | next: 55 | if (gr_mbuf_is_traced(mbuf)) { 56 | t = gr_mbuf_trace_add(mbuf, node, sizeof(*t)); 57 | *t = *arp; 58 | } 59 | rte_node_enqueue_x1(graph, node, edge, mbuf); 60 | } 61 | 62 | return nb_objs; 63 | } 64 | 65 | static void arp_input_register(void) { 66 | gr_eth_input_add_type(RTE_BE16(RTE_ETHER_TYPE_ARP), "arp_input"); 67 | } 68 | 69 | static struct rte_node_register node = { 70 | .name = "arp_input", 71 | 72 | .process = arp_input_process, 73 | 74 | .nb_edges = EDGE_COUNT, 75 | .next_nodes = { 76 | [OP_REQUEST] = "arp_input_request", 77 | [OP_REPLY] = "arp_input_reply", 78 | [OP_UNSUPP] = "arp_input_op_unsupp", 79 | [PROTO_UNSUPP] = "arp_input_proto_unsupp", 80 | }, 81 | }; 82 | 83 | static struct gr_node_info info = { 84 | .node = &node, 85 | .register_callback = arp_input_register, 86 | .trace_format = (gr_trace_format_cb_t)trace_arp_format, 87 | }; 88 | 89 | GR_NODE_REGISTER(info); 90 | 91 | GR_DROP_REGISTER(arp_input_op_unsupp); 92 | GR_DROP_REGISTER(arp_input_proto_unsupp); 93 | -------------------------------------------------------------------------------- /modules/ip/datapath/arp_input_reply.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2024 Robin Jarry 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | enum { 17 | CONTROL = 0, 18 | DROP, 19 | EDGE_COUNT, 20 | }; 21 | 22 | static uint16_t arp_input_reply_process( 23 | struct rte_graph *graph, 24 | struct rte_node *node, 25 | void **objs, 26 | uint16_t nb_objs 27 | ) { 28 | const struct iface *iface; 29 | struct rte_arp_hdr *arp; 30 | struct nexthop *remote; 31 | struct rte_mbuf *mbuf; 32 | 33 | for (uint16_t i = 0; i < nb_objs; i++) { 34 | mbuf = objs[i]; 35 | 36 | arp = rte_pktmbuf_mtod(mbuf, struct rte_arp_hdr *); 37 | iface = mbuf_data(mbuf)->iface; 38 | remote = nh4_lookup(iface->vrf_id, arp->arp_data.arp_sip); 39 | 40 | if (gr_mbuf_is_traced(mbuf)) 41 | gr_mbuf_trace_add(mbuf, node, 0); 42 | 43 | if (remote != NULL) { 44 | struct control_output_mbuf_data *d = control_output_mbuf_data(mbuf); 45 | d->callback = arp_probe_input_cb; 46 | d->iface = iface; 47 | rte_node_enqueue_x1(graph, node, CONTROL, mbuf); 48 | } else { 49 | rte_node_enqueue_x1(graph, node, DROP, mbuf); 50 | } 51 | } 52 | 53 | return nb_objs; 54 | } 55 | 56 | static struct rte_node_register node = { 57 | .name = "arp_input_reply", 58 | 59 | .process = arp_input_reply_process, 60 | 61 | .nb_edges = EDGE_COUNT, 62 | .next_nodes = { 63 | [CONTROL] = "control_output", 64 | [DROP] = "arp_input_reply_drop", 65 | }, 66 | }; 67 | 68 | static struct gr_node_info info = { 69 | .node = &node, 70 | }; 71 | 72 | GR_NODE_REGISTER(info); 73 | 74 | GR_DROP_REGISTER(arp_input_reply_drop); 75 | -------------------------------------------------------------------------------- /modules/ip/datapath/arp_input_request.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2024 Robin Jarry 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | enum { 17 | CONTROL = 0, 18 | DROP, 19 | ERROR, 20 | EDGE_COUNT, 21 | }; 22 | 23 | static uint16_t arp_input_request_process( 24 | struct rte_graph *graph, 25 | struct rte_node *node, 26 | void **objs, 27 | uint16_t nb_objs 28 | ) { 29 | struct control_output_mbuf_data *ctrl_data; 30 | const struct nexthop *local; 31 | const struct iface *iface; 32 | struct rte_arp_hdr *arp; 33 | struct rte_mbuf *mbuf; 34 | rte_edge_t edge; 35 | 36 | for (uint16_t i = 0; i < nb_objs; i++) { 37 | mbuf = objs[i]; 38 | 39 | arp = rte_pktmbuf_mtod(mbuf, struct rte_arp_hdr *); 40 | iface = mbuf_data(mbuf)->iface; 41 | local = nh4_lookup(iface->vrf_id, arp->arp_data.arp_tip); 42 | if (local == NULL || !(local->flags & GR_NH_F_LOCAL)) { 43 | // ARP request not for us 44 | edge = DROP; 45 | goto next; 46 | } 47 | 48 | ctrl_data = control_output_mbuf_data(mbuf); 49 | ctrl_data->callback = arp_probe_input_cb; 50 | ctrl_data->iface = iface; 51 | edge = CONTROL; 52 | next: 53 | if (gr_mbuf_is_traced(mbuf)) 54 | gr_mbuf_trace_add(mbuf, node, 0); 55 | rte_node_enqueue_x1(graph, node, edge, mbuf); 56 | } 57 | 58 | return nb_objs; 59 | } 60 | 61 | static struct rte_node_register node = { 62 | .name = "arp_input_request", 63 | 64 | .process = arp_input_request_process, 65 | 66 | .nb_edges = EDGE_COUNT, 67 | .next_nodes = { 68 | [CONTROL] = "control_output", 69 | [DROP] = "arp_input_request_drop", 70 | [ERROR] = "arp_input_request_error", 71 | }, 72 | }; 73 | 74 | static struct gr_node_info info = { 75 | .node = &node, 76 | }; 77 | 78 | GR_NODE_REGISTER(info); 79 | 80 | GR_DROP_REGISTER(arp_input_request_drop); 81 | GR_DROP_REGISTER(arp_input_request_error); 82 | -------------------------------------------------------------------------------- /modules/ip/datapath/arp_output_reply.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2024 Robin Jarry 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | enum { 18 | OUTPUT = 0, 19 | ERROR, 20 | EDGE_COUNT, 21 | }; 22 | 23 | static uint16_t arp_output_reply_process( 24 | struct rte_graph *graph, 25 | struct rte_node *node, 26 | void **objs, 27 | uint16_t nb_objs 28 | ) { 29 | struct eth_output_mbuf_data *eth_data; 30 | const struct nexthop *local; 31 | const struct iface *iface; 32 | struct rte_arp_hdr *arp; 33 | struct rte_mbuf *mbuf; 34 | rte_edge_t edge; 35 | uint16_t num; 36 | 37 | num = 0; 38 | 39 | for (uint16_t i = 0; i < nb_objs; i++) { 40 | mbuf = objs[i]; 41 | 42 | iface = mbuf_data(mbuf)->iface; 43 | local = arp_reply_mbuf_data(mbuf)->local; 44 | if (iface == NULL || local == NULL) { 45 | edge = ERROR; 46 | goto next; 47 | } 48 | 49 | // Reuse mbuf to craft an ARP reply. 50 | rte_pktmbuf_trim(mbuf, rte_pktmbuf_pkt_len(mbuf)); 51 | arp = (struct rte_arp_hdr *)rte_pktmbuf_append(mbuf, sizeof(*arp)); 52 | arp->arp_hardware = RTE_BE16(RTE_ARP_HRD_ETHER); 53 | arp->arp_protocol = RTE_BE16(RTE_ETHER_TYPE_IPV4); 54 | arp->arp_opcode = RTE_BE16(RTE_ARP_OP_REPLY); 55 | arp->arp_data.arp_tha = arp->arp_data.arp_sha; 56 | arp->arp_data.arp_sha = local->mac; 57 | arp->arp_data.arp_tip = arp->arp_data.arp_sip; 58 | arp->arp_data.arp_sip = local->ipv4; 59 | 60 | // Prepare ethernet layer info. 61 | eth_data = eth_output_mbuf_data(mbuf); 62 | eth_data->dst = arp->arp_data.arp_tha; 63 | eth_data->ether_type = RTE_BE16(RTE_ETHER_TYPE_ARP); 64 | eth_data->iface = iface; 65 | edge = OUTPUT; 66 | num++; 67 | next: 68 | if (gr_mbuf_is_traced(mbuf)) { 69 | struct rte_arp_hdr *t = gr_mbuf_trace_add(mbuf, node, sizeof(*t)); 70 | if (edge == OUTPUT) 71 | *t = *arp; 72 | else 73 | t->arp_opcode = 0; 74 | } 75 | rte_node_enqueue_x1(graph, node, edge, mbuf); 76 | } 77 | 78 | return num; 79 | } 80 | 81 | static struct rte_node_register node = { 82 | .name = "arp_output_reply", 83 | .process = arp_output_reply_process, 84 | .nb_edges = EDGE_COUNT, 85 | .next_nodes = { 86 | [OUTPUT] = "eth_output", 87 | [ERROR] = "arp_output_reply_error", 88 | }, 89 | }; 90 | 91 | static struct gr_node_info info = { 92 | .node = &node, 93 | .trace_format = (gr_trace_format_cb_t)trace_arp_format, 94 | }; 95 | 96 | GR_NODE_REGISTER(info); 97 | 98 | GR_DROP_REGISTER(arp_output_reply_error); 99 | -------------------------------------------------------------------------------- /modules/ip/datapath/gr_fib4.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2025 Robin Jarry 3 | 4 | #ifndef _GR_FIB4_H 5 | #define _GR_FIB4_H 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | // TODO: make this configurable 13 | #define IP4_MAX_ROUTES (1 << 16) 14 | 15 | // Only for datapath use 16 | const struct nexthop *fib4_lookup(uint16_t vrf_id, ip4_addr_t ip); 17 | 18 | // Only for control plane use to update the fib 19 | int fib4_insert(uint16_t vrf_id, ip4_addr_t ip, uint8_t prefixlen, const struct nexthop *); 20 | int fib4_remove(uint16_t vrf_id, ip4_addr_t ip, uint8_t prefixlen); 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /modules/ip/datapath/gr_ip4_datapath.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2024 Robin Jarry 3 | 4 | #ifndef _GR_IP4_DATAPATH_H 5 | #define _GR_IP4_DATAPATH_H 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | 16 | #include 17 | 18 | GR_MBUF_PRIV_DATA_TYPE(ip_output_mbuf_data, { const struct nexthop *nh; }); 19 | 20 | GR_MBUF_PRIV_DATA_TYPE(arp_reply_mbuf_data, { const struct nexthop *local; }); 21 | 22 | GR_MBUF_PRIV_DATA_TYPE(ip_local_mbuf_data, { 23 | ip4_addr_t src; 24 | ip4_addr_t dst; 25 | uint16_t len; 26 | uint16_t vrf_id; 27 | uint8_t proto; 28 | uint8_t ttl; 29 | }); 30 | 31 | void ip_input_local_add_proto(uint8_t proto, const char *next_node); 32 | void ip_output_register_interface_type(gr_iface_type_t type, const char *next_node); 33 | void ip_output_register_nexthop_type(gr_nh_type_t type, const char *next_node); 34 | int arp_output_request_solicit(struct nexthop *nh); 35 | void arp_update_nexthop( 36 | struct rte_graph *graph, 37 | struct rte_node *node, 38 | struct nexthop *nh, 39 | const struct iface *iface, 40 | const struct rte_ether_addr *mac 41 | ); 42 | 43 | #define IPV4_VERSION_IHL 0x45 44 | #define IPV4_DEFAULT_TTL 64 45 | 46 | static inline void ip_set_fields(struct rte_ipv4_hdr *ip, struct ip_local_mbuf_data *data) { 47 | ip->version_ihl = IPV4_VERSION_IHL; 48 | ip->type_of_service = 0; 49 | ip->total_length = rte_cpu_to_be_16(data->len + rte_ipv4_hdr_len(ip)); 50 | ip->fragment_offset = 0; 51 | ip->packet_id = 0; 52 | ip->time_to_live = data->ttl ?: IPV4_DEFAULT_TTL; 53 | ip->next_proto_id = data->proto; 54 | ip->src_addr = data->src; 55 | ip->dst_addr = data->dst; 56 | ip->hdr_checksum = 0; 57 | ip->hdr_checksum = rte_ipv4_cksum(ip); 58 | } 59 | 60 | int icmp_local_send( 61 | uint16_t vrf_id, 62 | ip4_addr_t dst, 63 | const struct nexthop *gw, 64 | uint16_t ident, 65 | uint16_t seq_num, 66 | uint8_t ttl 67 | ); 68 | 69 | void icmp_input_register_callback(uint8_t icmp_type, control_output_cb_t cb); 70 | 71 | #endif 72 | -------------------------------------------------------------------------------- /modules/ip/datapath/icmp_output.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2024 Robin Jarry 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | #include 15 | 16 | enum { 17 | OUTPUT = 0, 18 | NO_HEADROOM, 19 | NO_ROUTE, 20 | EDGE_COUNT, 21 | }; 22 | 23 | static uint16_t 24 | icmp_output_process(struct rte_graph *graph, struct rte_node *node, void **objs, uint16_t nb_objs) { 25 | struct ip_local_mbuf_data *local_data; 26 | struct ip_output_mbuf_data *o; 27 | struct rte_icmp_hdr *icmp; 28 | const struct nexthop *nh; 29 | struct rte_ipv4_hdr *ip; 30 | struct rte_mbuf *mbuf; 31 | rte_edge_t edge; 32 | 33 | for (uint16_t i = 0; i < nb_objs; i++) { 34 | mbuf = objs[i]; 35 | local_data = ip_local_mbuf_data(mbuf); 36 | 37 | icmp = rte_pktmbuf_mtod(mbuf, struct rte_icmp_hdr *); 38 | icmp->icmp_cksum = 0; 39 | icmp->icmp_cksum = ~rte_raw_cksum(icmp, local_data->len); 40 | 41 | ip = (struct rte_ipv4_hdr *)rte_pktmbuf_prepend(mbuf, sizeof(*ip)); 42 | if (unlikely(ip == NULL)) { 43 | edge = NO_HEADROOM; 44 | goto next; 45 | } 46 | ip_set_fields(ip, local_data); 47 | if ((nh = fib4_lookup(local_data->vrf_id, local_data->dst)) == NULL) { 48 | // Do not let packets go to ip_output from icmp_output 49 | // with no available route to avoid loops of destination 50 | // unreachable errors. 51 | edge = NO_ROUTE; 52 | goto next; 53 | } 54 | o = ip_output_mbuf_data(mbuf); 55 | o->nh = nh; 56 | o->iface = NULL; 57 | edge = OUTPUT; 58 | next: 59 | rte_node_enqueue_x1(graph, node, edge, mbuf); 60 | } 61 | 62 | return nb_objs; 63 | } 64 | 65 | static struct rte_node_register icmp_output_node = { 66 | .name = "icmp_output", 67 | 68 | .process = icmp_output_process, 69 | 70 | .nb_edges = EDGE_COUNT, 71 | .next_nodes = { 72 | [OUTPUT] = "ip_output", 73 | [NO_HEADROOM] = "error_no_headroom", 74 | [NO_ROUTE] = "icmp_output_no_route", 75 | }, 76 | }; 77 | 78 | static struct gr_node_info icmp_output_info = { 79 | .node = &icmp_output_node, 80 | }; 81 | 82 | GR_NODE_REGISTER(icmp_output_info); 83 | 84 | GR_DROP_REGISTER(icmp_output_no_route); 85 | -------------------------------------------------------------------------------- /modules/ip/datapath/ip_forward.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2024 Robin Jarry 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | enum edges { 13 | OUTPUT = 0, 14 | TTL_EXCEEDED, 15 | EDGE_COUNT, 16 | }; 17 | 18 | static uint16_t 19 | ip_forward_process(struct rte_graph *graph, struct rte_node *node, void **objs, uint16_t nb_objs) { 20 | struct rte_ipv4_hdr *ip; 21 | struct rte_mbuf *mbuf; 22 | rte_be32_t csum; 23 | uint16_t i; 24 | 25 | for (i = 0; i < nb_objs; i++) { 26 | mbuf = objs[i]; 27 | ip = rte_pktmbuf_mtod(mbuf, struct rte_ipv4_hdr *); 28 | 29 | if (ip->time_to_live <= 1) { 30 | rte_node_enqueue_x1(graph, node, TTL_EXCEEDED, mbuf); 31 | continue; 32 | } 33 | ip->time_to_live -= 1; 34 | csum = ip->hdr_checksum + RTE_BE16(0x0100); 35 | csum += csum >= 0xffff; 36 | ip->hdr_checksum = csum; 37 | 38 | if (gr_mbuf_is_traced(mbuf)) 39 | gr_mbuf_trace_add(mbuf, node, 0); 40 | 41 | rte_node_enqueue_x1(graph, node, OUTPUT, mbuf); 42 | } 43 | 44 | return nb_objs; 45 | } 46 | 47 | static struct rte_node_register forward_node = { 48 | .name = "ip_forward", 49 | 50 | .process = ip_forward_process, 51 | 52 | .nb_edges = EDGE_COUNT, 53 | .next_nodes = { 54 | [OUTPUT] = "ip_output", 55 | [TTL_EXCEEDED] = "ip_error_ttl_exceeded", 56 | }, 57 | }; 58 | 59 | static struct gr_node_info info = { 60 | .node = &forward_node, 61 | }; 62 | 63 | GR_NODE_REGISTER(info); 64 | -------------------------------------------------------------------------------- /modules/ip/datapath/ip_hold.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2024 Robin Jarry 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | enum { 13 | CONTROL = 0, 14 | EDGE_COUNT, 15 | }; 16 | 17 | static uint16_t 18 | ip_hold_process(struct rte_graph *graph, struct rte_node *node, void **objs, uint16_t nb_objs) { 19 | struct control_output_mbuf_data *d; 20 | struct rte_mbuf *mbuf; 21 | 22 | for (uint16_t i = 0; i < nb_objs; i++) { 23 | mbuf = objs[i]; 24 | // TODO: Allocate a new mbuf from a control plane pool and copy 25 | // the packet into it so that the datapath mbuf can be freed and 26 | // returned to the stack for hardware RX. 27 | d = control_output_mbuf_data(mbuf); 28 | d->callback = nh4_unreachable_cb; 29 | if (gr_mbuf_is_traced(mbuf)) 30 | gr_mbuf_trace_add(mbuf, node, 0); 31 | rte_node_enqueue_x1(graph, node, CONTROL, mbuf); 32 | } 33 | 34 | return nb_objs; 35 | } 36 | 37 | static struct rte_node_register node = { 38 | .name = "ip_hold", 39 | .process = ip_hold_process, 40 | .nb_edges = EDGE_COUNT, 41 | .next_nodes = { 42 | [CONTROL] = "control_output", 43 | }, 44 | }; 45 | 46 | static struct gr_node_info info = { 47 | .node = &node, 48 | }; 49 | 50 | GR_NODE_REGISTER(info); 51 | -------------------------------------------------------------------------------- /modules/ip/datapath/ip_local.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2024 Robin Jarry 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | #define UNKNOWN_PROTO 0 14 | static rte_edge_t edges[256] = {UNKNOWN_PROTO}; 15 | 16 | void ip_input_local_add_proto(uint8_t proto, const char *next_node) { 17 | LOG(DEBUG, "ip_input_local: proto=%hhu -> %s", proto, next_node); 18 | if (edges[proto] != UNKNOWN_PROTO) 19 | ABORT("next node already registered for proto=%hhu", proto); 20 | edges[proto] = gr_node_attach_parent("ip_input_local", next_node); 21 | } 22 | 23 | static uint16_t ip_input_local_process( 24 | struct rte_graph *graph, 25 | struct rte_node *node, 26 | void **objs, 27 | uint16_t nb_objs 28 | ) { 29 | struct rte_ipv4_hdr *ip; 30 | struct rte_mbuf *mbuf; 31 | rte_edge_t edge; 32 | uint16_t i; 33 | 34 | for (i = 0; i < nb_objs; i++) { 35 | mbuf = objs[i]; 36 | ip = rte_pktmbuf_mtod(mbuf, struct rte_ipv4_hdr *); 37 | 38 | if (gr_mbuf_is_traced(mbuf)) 39 | gr_mbuf_trace_add(mbuf, node, 0); 40 | 41 | edge = edges[ip->next_proto_id]; 42 | if (edge != UNKNOWN_PROTO) { 43 | const struct iface *iface = ip_output_mbuf_data(mbuf)->iface; 44 | struct ip_local_mbuf_data *data = ip_local_mbuf_data(mbuf); 45 | data->src = ip->src_addr; 46 | data->dst = ip->dst_addr; 47 | data->len = rte_be_to_cpu_16(ip->total_length) - rte_ipv4_hdr_len(ip); 48 | data->vrf_id = iface->vrf_id; 49 | data->proto = ip->next_proto_id; 50 | rte_pktmbuf_adj(mbuf, sizeof(*ip)); 51 | } 52 | rte_node_enqueue_x1(graph, node, edge, mbuf); 53 | } 54 | 55 | return nb_objs; 56 | } 57 | 58 | static struct rte_node_register input_node = { 59 | .name = "ip_input_local", 60 | .process = ip_input_local_process, 61 | .nb_edges = 1, 62 | .next_nodes = { 63 | [UNKNOWN_PROTO] = "ip_input_local_unknown_proto", 64 | }, 65 | }; 66 | 67 | static struct gr_node_info info = { 68 | .node = &input_node, 69 | }; 70 | 71 | GR_NODE_REGISTER(info); 72 | 73 | GR_DROP_REGISTER(ip_input_local_unknown_proto); 74 | -------------------------------------------------------------------------------- /modules/ip/datapath/meson.build: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Copyright (c) 2024 Robin Jarry 3 | 4 | src += files( 5 | 'arp_input.c', 6 | 'arp_input_reply.c', 7 | 'arp_input_request.c', 8 | 'arp_output_reply.c', 9 | 'arp_output_request.c', 10 | 'fib4.c', 11 | 'icmp_input.c', 12 | 'icmp_local_send.c', 13 | 'icmp_output.c', 14 | 'ip_error.c', 15 | 'ip_forward.c', 16 | 'ip_hold.c', 17 | 'ip_input.c', 18 | 'ip_local.c', 19 | 'ip_output.c', 20 | ) 21 | inc += include_directories('.') 22 | 23 | tests += [ 24 | { 25 | 'sources': files('ip_input.c'), 26 | 'link_args': [], 27 | } 28 | ] 29 | -------------------------------------------------------------------------------- /modules/ip/meson.build: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Copyright (c) 2024 Robin Jarry 3 | 4 | subdir('api') 5 | subdir('cli') 6 | subdir('control') 7 | subdir('datapath') 8 | -------------------------------------------------------------------------------- /modules/ip6/api/meson.build: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Copyright (c) 2024 Robin Jarry 3 | 4 | api_headers += files('gr_ip6.h') 5 | api_inc += include_directories('.') 6 | -------------------------------------------------------------------------------- /modules/ip6/cli/ip.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2024 Robin Jarry 3 | 4 | #ifndef _GR_CLI_IP 5 | #define _GR_CLI_IP 6 | 7 | #include 8 | 9 | #define IP6_ADD_CTX(root) CLI_CONTEXT(root, CTX_ADD, CTX_ARG("ip6", "Create IPv6 stack elements.")) 10 | #define IP6_DEL_CTX(root) CLI_CONTEXT(root, CTX_DEL, CTX_ARG("ip6", "Delete IPv6 stack elements.")) 11 | #define IP6_SHOW_CTX(root) CLI_CONTEXT(root, CTX_SHOW, CTX_ARG("ip6", "Show IPv6 stack details.")) 12 | #define IP6_SET_CTX(root) CLI_CONTEXT(root, CTX_SET, CTX_ARG("ip6", "Set IPv6 stack elements.")) 13 | #define IP6_CLEAR_CTX(root) \ 14 | CLI_CONTEXT(root, CTX_CLEAR, CTX_ARG("ip6", "Clear IPv6 stack elements.")) 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /modules/ip6/cli/meson.build: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Copyright (c) 2024 Robin Jarry 3 | 4 | cli_src += files( 5 | 'address.c', 6 | 'nexthop.c', 7 | 'route.c', 8 | 'router_advert.c', 9 | 'icmp6.c', 10 | ) 11 | -------------------------------------------------------------------------------- /modules/ip6/control/gr_ip6_control.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2024 Robin Jarry 3 | 4 | #ifndef _GR_IP6_CONTROL 5 | #define _GR_IP6_CONTROL 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | 19 | static inline struct nexthop * 20 | nh6_new(uint16_t vrf_id, uint16_t iface_id, const struct rte_ipv6_addr *ip) { 21 | return nexthop_new(GR_NH_IPV6, vrf_id, iface_id, ip); 22 | } 23 | 24 | static inline struct nexthop * 25 | nh6_lookup(uint16_t vrf_id, uint16_t iface_id, const struct rte_ipv6_addr *ip) { 26 | return nexthop_lookup(GR_NH_IPV6, vrf_id, iface_id, ip); 27 | } 28 | 29 | void nh6_unreachable_cb(struct rte_mbuf *m); 30 | void ndp_probe_input_cb(struct rte_mbuf *m); 31 | void ndp_router_sollicit_input_cb(struct rte_mbuf *m); 32 | 33 | int rib6_insert( 34 | uint16_t vrf_id, 35 | uint16_t iface_id, 36 | const struct rte_ipv6_addr *, 37 | uint8_t prefixlen, 38 | gr_rt_origin_t origin, 39 | struct nexthop *nh 40 | ); 41 | int rib6_delete( 42 | uint16_t vrf_id, 43 | uint16_t iface_id, 44 | const struct rte_ipv6_addr *, 45 | uint8_t prefixlen 46 | ); 47 | void rib6_cleanup(struct nexthop *); 48 | struct nexthop *rib6_lookup(uint16_t vrf_id, uint16_t iface_id, const struct rte_ipv6_addr *); 49 | struct nexthop *rib6_lookup_exact( 50 | uint16_t vrf_id, 51 | uint16_t iface_id, 52 | const struct rte_ipv6_addr *ip, 53 | uint8_t prefixlen 54 | ); 55 | 56 | // get the default address for a given interface 57 | struct nexthop *addr6_get_preferred(uint16_t iface_id, const struct rte_ipv6_addr *); 58 | // get the link-local address for a given interface 59 | struct nexthop *addr6_get_linklocal(uint16_t iface_id); 60 | // get all addresses for a given interface 61 | struct hoplist *addr6_get_all(uint16_t iface_id); 62 | // determine if the given interface is member of the provided multicast address group 63 | struct nexthop *mcast6_get_member(uint16_t iface_id, const struct rte_ipv6_addr *mcast); 64 | 65 | #endif 66 | -------------------------------------------------------------------------------- /modules/ip6/control/meson.build: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Copyright (c) 2024 Robin Jarry 3 | 4 | src += files( 5 | 'address.c', 6 | 'nexthop.c', 7 | 'route.c', 8 | 'router_advert.c', 9 | 'icmp6.c', 10 | ) 11 | inc += include_directories('.') 12 | -------------------------------------------------------------------------------- /modules/ip6/datapath/gr_fib6.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2025 Robin Jarry 3 | 4 | #ifndef _GR_FIB6_H 5 | #define _GR_FIB6_H 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | // TODO: make this configurable 13 | #define IP6_MAX_ROUTES (1 << 16) 14 | 15 | // Only for datapath use 16 | const struct nexthop * 17 | fib6_lookup(uint16_t vrf_id, uint16_t iface_id, const struct rte_ipv6_addr *ip); 18 | 19 | // Only for control plane use to update the fib 20 | int fib6_insert( 21 | uint16_t vrf_id, 22 | uint16_t iface_id, 23 | const struct rte_ipv6_addr *ip, 24 | uint8_t prefixlen, 25 | const struct nexthop *nh 26 | ); 27 | int fib6_remove( 28 | uint16_t vrf_id, 29 | uint16_t iface_id, 30 | const struct rte_ipv6_addr *ip, 31 | uint8_t prefixlen 32 | ); 33 | 34 | static inline const struct rte_ipv6_addr *addr6_linklocal_scope( 35 | const struct rte_ipv6_addr *ip, 36 | struct rte_ipv6_addr *scoped_ip, 37 | uint16_t iface_id 38 | ) { 39 | if (rte_ipv6_addr_is_linklocal(ip)) { 40 | *scoped_ip = *ip; 41 | scoped_ip->a[2] = (iface_id >> 8) & 0xff; 42 | scoped_ip->a[3] = iface_id & 0xff; 43 | return scoped_ip; 44 | } else { 45 | return ip; 46 | } 47 | } 48 | 49 | static inline const struct rte_ipv6_addr * 50 | addr6_linklocal_unscope(const struct rte_ipv6_addr *ip, struct rte_ipv6_addr *unscoped_ip) { 51 | if (rte_ipv6_addr_is_linklocal(ip)) { 52 | *unscoped_ip = *ip; 53 | unscoped_ip->a[2] = 0; 54 | unscoped_ip->a[3] = 0; 55 | return unscoped_ip; 56 | } else { 57 | return ip; 58 | } 59 | } 60 | 61 | #endif 62 | -------------------------------------------------------------------------------- /modules/ip6/datapath/gr_ip6_datapath.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2024 Robin Jarry 3 | 4 | #ifndef _GR_IP6_DATAPATH_H 5 | #define _GR_IP6_DATAPATH_H 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | #include 19 | 20 | GR_MBUF_PRIV_DATA_TYPE(ip6_output_mbuf_data, { const struct nexthop *nh; }); 21 | 22 | GR_MBUF_PRIV_DATA_TYPE(ip6_local_mbuf_data, { 23 | struct rte_ipv6_addr src; 24 | struct rte_ipv6_addr dst; 25 | uint16_t len; 26 | uint16_t ext_offset; 27 | uint8_t hop_limit; 28 | uint8_t proto; 29 | }); 30 | 31 | GR_MBUF_PRIV_DATA_TYPE(ndp_na_output_mbuf_data, { 32 | const struct nexthop *local; 33 | const struct nexthop *remote; 34 | }); 35 | 36 | void ip6_input_local_add_proto(uint8_t proto, const char *next_node); 37 | void ip6_output_register_interface_type(gr_iface_type_t type, const char *next_node); 38 | void ip6_output_register_nexthop_type(gr_nh_type_t type, const char *next_node); 39 | int nh6_solicit(struct nexthop *nh); 40 | 41 | #define IP6_DEFAULT_HOP_LIMIT 255 42 | 43 | static inline void ip6_set_fields( 44 | struct rte_ipv6_hdr *ip, 45 | uint16_t len, 46 | uint8_t proto, 47 | const struct rte_ipv6_addr *src, 48 | const struct rte_ipv6_addr *dst 49 | ) { 50 | ip->vtc_flow = RTE_BE32(0x60000000); 51 | ip->payload_len = rte_cpu_to_be_16(len); 52 | ip->proto = proto; 53 | ip->hop_limits = IP6_DEFAULT_HOP_LIMIT; 54 | ip->src_addr = *src; 55 | ip->dst_addr = *dst; 56 | } 57 | 58 | void ndp_update_nexthop( 59 | struct rte_graph *graph, 60 | struct rte_node *node, 61 | struct nexthop *nh, 62 | const struct iface *iface, 63 | const struct rte_ether_addr *mac 64 | ); 65 | 66 | int icmp6_local_send( 67 | const struct rte_ipv6_addr *dst, 68 | const struct nexthop *gw, 69 | uint16_t ident, 70 | uint16_t seq_num, 71 | uint8_t hop_limit 72 | ); 73 | 74 | void icmp6_input_register_callback(uint8_t icmp6_type, control_output_cb_t cb); 75 | 76 | #endif 77 | -------------------------------------------------------------------------------- /modules/ip6/datapath/icmp6_output.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2024 Robin Jarry 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | #include 16 | 17 | enum { 18 | OUTPUT = 0, 19 | NO_HEADROOM, 20 | NO_ROUTE, 21 | EDGE_COUNT, 22 | }; 23 | 24 | static uint16_t icmp6_output_process( 25 | struct rte_graph *graph, 26 | struct rte_node *node, 27 | void **objs, 28 | uint16_t nb_objs 29 | ) { 30 | struct ip6_output_mbuf_data *o; 31 | struct ip6_local_mbuf_data *d; 32 | const struct nexthop *nh; 33 | struct rte_ipv6_hdr *ip; 34 | struct rte_mbuf *mbuf; 35 | struct icmp6 *icmp6; 36 | rte_edge_t edge; 37 | 38 | for (uint16_t i = 0; i < nb_objs; i++) { 39 | mbuf = objs[i]; 40 | d = ip6_local_mbuf_data(mbuf); 41 | 42 | icmp6 = rte_pktmbuf_mtod(mbuf, struct icmp6 *); 43 | ip = (struct rte_ipv6_hdr *)rte_pktmbuf_prepend(mbuf, sizeof(*ip)); 44 | if (unlikely(ip == NULL)) { 45 | edge = NO_HEADROOM; 46 | goto next; 47 | } 48 | ip6_set_fields(ip, d->len, IPPROTO_ICMPV6, &d->src, &d->dst); 49 | // Compute ICMP6 checksum with pseudo header 50 | icmp6->cksum = 0; 51 | icmp6->cksum = rte_ipv6_udptcp_cksum(ip, icmp6); 52 | 53 | if (gr_mbuf_is_traced(mbuf)) { 54 | uint8_t trace_len = RTE_MIN(d->len, GR_TRACE_ITEM_MAX_LEN); 55 | struct icmp6 *t = gr_mbuf_trace_add(mbuf, node, trace_len); 56 | memcpy(t, icmp6, trace_len); 57 | } 58 | 59 | if (rte_ipv6_addr_is_mcast(&d->dst)) 60 | nh = nh6_lookup(d->iface->vrf_id, d->iface->id, &d->src); 61 | else 62 | nh = fib6_lookup(d->iface->vrf_id, d->iface->id, &d->dst); 63 | 64 | if (nh == NULL) { 65 | edge = NO_ROUTE; 66 | goto next; 67 | } 68 | o = ip6_output_mbuf_data(mbuf); 69 | o->nh = nh; 70 | o->iface = d->iface; 71 | edge = OUTPUT; 72 | next: 73 | rte_node_enqueue_x1(graph, node, edge, mbuf); 74 | } 75 | 76 | return nb_objs; 77 | } 78 | 79 | static struct rte_node_register icmp6_output_node = { 80 | .name = "icmp6_output", 81 | 82 | .process = icmp6_output_process, 83 | 84 | .nb_edges = EDGE_COUNT, 85 | .next_nodes = { 86 | [OUTPUT] = "ip6_output", 87 | [NO_HEADROOM] = "error_no_headroom", 88 | [NO_ROUTE] = "icmp6_output_no_route", 89 | }, 90 | }; 91 | 92 | static struct gr_node_info icmp6_output_info = { 93 | .node = &icmp6_output_node, 94 | .trace_format = (gr_trace_format_cb_t)trace_icmp6_format, 95 | }; 96 | 97 | GR_NODE_REGISTER(icmp6_output_info); 98 | 99 | GR_DROP_REGISTER(icmp6_output_no_route) 100 | -------------------------------------------------------------------------------- /modules/ip6/datapath/ip6_forward.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2024 Robin Jarry 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | enum edges { 13 | OUTPUT = 0, 14 | TTL_EXCEEDED, 15 | EDGE_COUNT, 16 | }; 17 | 18 | static uint16_t 19 | ip6_forward_process(struct rte_graph *graph, struct rte_node *node, void **objs, uint16_t nb_objs) { 20 | struct rte_ipv6_hdr *ip; 21 | struct rte_mbuf *mbuf; 22 | uint16_t i; 23 | 24 | for (i = 0; i < nb_objs; i++) { 25 | mbuf = objs[i]; 26 | ip = rte_pktmbuf_mtod(mbuf, struct rte_ipv6_hdr *); 27 | if (gr_mbuf_is_traced(mbuf)) 28 | gr_mbuf_trace_add(mbuf, node, 0); 29 | 30 | if (ip->hop_limits <= 1) { 31 | rte_node_enqueue_x1(graph, node, TTL_EXCEEDED, mbuf); 32 | continue; 33 | } 34 | ip->hop_limits -= 1; 35 | rte_node_enqueue_x1(graph, node, OUTPUT, mbuf); 36 | } 37 | 38 | return nb_objs; 39 | } 40 | 41 | static struct rte_node_register node = { 42 | .name = "ip6_forward", 43 | 44 | .process = ip6_forward_process, 45 | 46 | .nb_edges = EDGE_COUNT, 47 | .next_nodes = { 48 | [OUTPUT] = "ip6_output", 49 | [TTL_EXCEEDED] = "ip6_error_ttl_exceeded", 50 | }, 51 | }; 52 | 53 | static struct gr_node_info info = { 54 | .node = &node, 55 | }; 56 | 57 | GR_NODE_REGISTER(info); 58 | -------------------------------------------------------------------------------- /modules/ip6/datapath/ip6_hold.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2024 Robin Jarry 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | enum { 12 | CONTROL = 0, 13 | EDGE_COUNT, 14 | }; 15 | 16 | static uint16_t 17 | ip6_hold_process(struct rte_graph *graph, struct rte_node *node, void **objs, uint16_t nb_objs) { 18 | struct control_output_mbuf_data *d; 19 | struct rte_mbuf *mbuf; 20 | 21 | for (uint16_t i = 0; i < nb_objs; i++) { 22 | mbuf = objs[i]; 23 | d = control_output_mbuf_data(mbuf); 24 | d->callback = nh6_unreachable_cb; 25 | if (gr_mbuf_is_traced(mbuf)) 26 | gr_mbuf_trace_add(mbuf, node, 0); 27 | rte_node_enqueue_x1(graph, node, CONTROL, mbuf); 28 | } 29 | 30 | return nb_objs; 31 | } 32 | 33 | static struct rte_node_register node = { 34 | .name = "ip6_hold", 35 | .process = ip6_hold_process, 36 | .nb_edges = EDGE_COUNT, 37 | .next_nodes = { 38 | [CONTROL] = "control_output", 39 | }, 40 | }; 41 | 42 | static struct gr_node_info info = { 43 | .node = &node, 44 | }; 45 | 46 | GR_NODE_REGISTER(info); 47 | -------------------------------------------------------------------------------- /modules/ip6/datapath/meson.build: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Copyright (c) 2024 Robin Jarry 3 | 4 | src += files( 5 | 'fib6.c', 6 | 'icmp6_input.c', 7 | 'icmp6_output.c', 8 | 'icmp6_local_send.c', 9 | 'ip6_error.c', 10 | 'ip6_forward.c', 11 | 'ip6_hold.c', 12 | 'ip6_input.c', 13 | 'ip6_local.c', 14 | 'ip6_output.c', 15 | 'ndp_na_input.c', 16 | 'ndp_na_output.c', 17 | 'ndp_ns_input.c', 18 | 'ndp_ns_output.c', 19 | 'ndp_rs_input.c', 20 | ) 21 | inc += include_directories('.') 22 | 23 | tests += [ 24 | { 25 | 'sources': files('ip6_input.c'), 26 | 'link_args': [], 27 | }, 28 | { 29 | 'sources': files('ndp_na_input.c'), 30 | 'link_args': [], 31 | }, 32 | ] 33 | -------------------------------------------------------------------------------- /modules/ip6/datapath/ndp_na_output.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2024 Robin Jarry 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | enum { 18 | OUTPUT = 0, 19 | EDGE_COUNT, 20 | }; 21 | 22 | static uint16_t ndp_na_output_process( 23 | struct rte_graph *graph, 24 | struct rte_node *node, 25 | void **objs, 26 | uint16_t nb_objs 27 | ) { 28 | const struct nexthop *local, *remote; 29 | struct ip6_local_mbuf_data *d; 30 | struct icmp6_neigh_advert *na; 31 | struct icmp6_opt_lladdr *ll; 32 | struct icmp6_opt *opt; 33 | struct rte_mbuf *mbuf; 34 | uint16_t payload_len; 35 | struct icmp6 *icmp6; 36 | 37 | for (uint16_t i = 0; i < nb_objs; i++) { 38 | mbuf = objs[i]; 39 | 40 | local = ndp_na_output_mbuf_data(mbuf)->local; 41 | remote = ndp_na_output_mbuf_data(mbuf)->remote; 42 | 43 | rte_pktmbuf_trim(mbuf, rte_pktmbuf_pkt_len(mbuf)); 44 | 45 | // Fill ICMP6 layer. 46 | payload_len = sizeof(*icmp6) + sizeof(*na) + sizeof(*opt) + sizeof(*ll); 47 | icmp6 = (struct icmp6 *)rte_pktmbuf_append(mbuf, payload_len); 48 | icmp6->type = ICMP6_TYPE_NEIGH_ADVERT; 49 | icmp6->code = 0; 50 | na = PAYLOAD(icmp6); 51 | na->override = 1; 52 | na->router = 1; 53 | na->solicited = remote != NULL; 54 | na->target = local->ipv6; 55 | opt = PAYLOAD(na); 56 | opt->type = ICMP6_OPT_TARGET_LLADDR; 57 | opt->len = ICMP6_OPT_LEN(sizeof(*opt) + sizeof(*ll)); 58 | ll = PAYLOAD(opt); 59 | ll->mac = local->mac; 60 | 61 | // Fill in IP local data 62 | d = ip6_local_mbuf_data(mbuf); 63 | d->iface = iface_from_id(local->iface_id); 64 | d->src = local->ipv6; 65 | if (remote == NULL) { 66 | // If the source of the solicitation is the unspecified address, the 67 | // node MUST set the Solicited flag to zero and multicast the 68 | // advertisement to the all-nodes address. 69 | d->dst = (struct rte_ipv6_addr)RTE_IPV6_ADDR_ALLNODES_LINK_LOCAL; 70 | } else { 71 | d->dst = remote->ipv6; 72 | } 73 | d->len = payload_len; 74 | d->hop_limit = IP6_DEFAULT_HOP_LIMIT; 75 | d->proto = IPPROTO_ICMPV6; 76 | 77 | if (gr_mbuf_is_traced(mbuf)) { 78 | uint8_t trace_len = RTE_MIN(payload_len, GR_TRACE_ITEM_MAX_LEN); 79 | struct icmp6 *t = gr_mbuf_trace_add(mbuf, node, trace_len); 80 | memcpy(t, icmp6, trace_len); 81 | } 82 | rte_node_enqueue_x1(graph, node, OUTPUT, mbuf); 83 | } 84 | 85 | return nb_objs; 86 | } 87 | 88 | static struct rte_node_register node = { 89 | .name = "ndp_na_output", 90 | 91 | .process = ndp_na_output_process, 92 | 93 | .nb_edges = EDGE_COUNT, 94 | .next_nodes = { 95 | [OUTPUT] = "icmp6_output", 96 | }, 97 | }; 98 | 99 | static struct gr_node_info info = { 100 | .node = &node, 101 | .trace_format = (gr_trace_format_cb_t)trace_icmp6_format, 102 | }; 103 | 104 | GR_NODE_REGISTER(info); 105 | -------------------------------------------------------------------------------- /modules/ip6/datapath/ndp_rs_input.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2025 Christophe Fontaine 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | enum { 18 | CONTROL, 19 | INVAL, 20 | EDGE_COUNT, 21 | }; 22 | 23 | static uint16_t ndp_rs_input_process( 24 | struct rte_graph *graph, 25 | struct rte_node *node, 26 | void **objs, 27 | uint16_t nb_objs 28 | ) { 29 | struct control_output_mbuf_data *co; 30 | struct ip6_local_mbuf_data *d; 31 | struct rte_mbuf *mbuf; 32 | struct icmp6 *icmp6; 33 | rte_edge_t next; 34 | 35 | #define ASSERT_NDP(condition) \ 36 | do { \ 37 | if (!(condition)) { \ 38 | next = INVAL; \ 39 | goto next; \ 40 | } \ 41 | } while (0) 42 | 43 | for (uint16_t i = 0; i < nb_objs; i++) { 44 | mbuf = objs[i]; 45 | 46 | d = ip6_local_mbuf_data(mbuf); 47 | icmp6 = rte_pktmbuf_mtod(mbuf, struct icmp6 *); 48 | 49 | // Validation of Router Solicitations 50 | // https://www.rfc-editor.org/rfc/rfc4861#section-6.1.1 51 | // 52 | // - The IP Hop Limit field has a value of 255, i.e., the packet 53 | // could not possibly have been forwarded by a router. 54 | ASSERT_NDP(d->hop_limit == 255); 55 | // - ICMP Checksum is valid. (already checked in icmp6_input) 56 | // 57 | // - ICMP Code is 0. 58 | ASSERT_NDP(icmp6->code == 0); 59 | // - ICMP length (derived from the IP length) is 8 or more octets. 60 | ASSERT_NDP(d->len >= 8); 61 | 62 | next = CONTROL; 63 | co = control_output_mbuf_data(mbuf); 64 | co->callback = ndp_router_sollicit_input_cb; 65 | next: 66 | if (gr_mbuf_is_traced(mbuf)) 67 | gr_mbuf_trace_add(mbuf, node, 0); 68 | rte_node_enqueue_x1(graph, node, next, mbuf); 69 | } 70 | 71 | return nb_objs; 72 | } 73 | 74 | static struct rte_node_register node = { 75 | .name = "ndp_rs_input", 76 | .process = ndp_rs_input_process, 77 | .nb_edges = EDGE_COUNT, 78 | .next_nodes = { 79 | [CONTROL] = "control_output", 80 | [INVAL] = "ndp_rs_input_inval", 81 | }, 82 | }; 83 | 84 | static struct gr_node_info info = { 85 | .node = &node, 86 | .trace_format = (gr_trace_format_cb_t)trace_icmp6_format, 87 | }; 88 | 89 | GR_NODE_REGISTER(info); 90 | 91 | GR_DROP_REGISTER(ndp_rs_input_inval); 92 | -------------------------------------------------------------------------------- /modules/ip6/meson.build: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Copyright (c) 2024 Robin Jarry 3 | 4 | subdir('api') 5 | subdir('cli') 6 | subdir('control') 7 | subdir('datapath') 8 | -------------------------------------------------------------------------------- /modules/ipip/datapath_in.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2024 Robin Jarry 3 | 4 | #include "ipip_priv.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | #include 18 | 19 | #include 20 | 21 | enum { 22 | IP_INPUT = 0, 23 | NO_TUNNEL, 24 | EDGE_COUNT, 25 | }; 26 | 27 | int trace_ipip_format(char *buf, size_t len, const void *data, size_t /*data_len*/) { 28 | const struct trace_ipip_data *t = data; 29 | const struct iface *iface = iface_from_id(t->iface_id); 30 | return snprintf(buf, len, "iface=%s", iface ? iface->name : "[deleted]"); 31 | } 32 | 33 | static uint16_t 34 | ipip_input_process(struct rte_graph *graph, struct rte_node *node, void **objs, uint16_t nb_objs) { 35 | struct eth_input_mbuf_data *eth_data; 36 | struct ip_local_mbuf_data *ip_data; 37 | ip4_addr_t last_src, last_dst; 38 | uint16_t last_vrf_id; 39 | struct rte_mbuf *mbuf; 40 | struct iface *ipip; 41 | rte_edge_t edge; 42 | 43 | ipip = NULL; 44 | last_src = 0; 45 | last_dst = 0; 46 | last_vrf_id = UINT16_MAX; 47 | 48 | for (uint16_t i = 0; i < nb_objs; i++) { 49 | mbuf = objs[i]; 50 | ip_data = ip_local_mbuf_data(mbuf); 51 | 52 | if (ip_data->dst != last_dst || ip_data->src != last_src 53 | || ip_data->vrf_id != last_vrf_id) { 54 | ipip = ipip_get_iface(ip_data->dst, ip_data->src, ip_data->vrf_id); 55 | last_dst = ip_data->dst; 56 | last_src = ip_data->src; 57 | last_vrf_id = ip_data->vrf_id; 58 | } 59 | if (ipip == NULL) { 60 | edge = NO_TUNNEL; 61 | goto next; 62 | } 63 | // The hw checksum offload only works on the outer IP. 64 | // Clear the offload flag so that ip_input will check it in software. 65 | mbuf->ol_flags |= RTE_MBUF_F_RX_IP_CKSUM_NONE; 66 | eth_data = eth_input_mbuf_data(mbuf); 67 | eth_data->iface = ipip; 68 | eth_data->domain = ETH_DOMAIN_LOCAL; 69 | edge = IP_INPUT; 70 | next: 71 | if (gr_mbuf_is_traced(mbuf) || (ipip && ipip->flags & GR_IFACE_F_PACKET_TRACE)) { 72 | struct trace_ipip_data *t = gr_mbuf_trace_add(mbuf, node, sizeof(*t)); 73 | t->iface_id = ipip ? ipip->id : 0; 74 | } 75 | rte_node_enqueue_x1(graph, node, edge, mbuf); 76 | } 77 | 78 | return nb_objs; 79 | } 80 | 81 | static void ipip_input_register(void) { 82 | ip_input_local_add_proto(IPPROTO_IPIP, "ipip_input"); 83 | } 84 | 85 | static struct rte_node_register ipip_input_node = { 86 | .name = "ipip_input", 87 | 88 | .process = ipip_input_process, 89 | 90 | .nb_edges = EDGE_COUNT, 91 | .next_nodes = { 92 | [IP_INPUT] = "ip_input", 93 | [NO_TUNNEL] = "ipip_input_no_tunnel", 94 | }, 95 | }; 96 | 97 | static struct gr_node_info ipip_input_info = { 98 | .node = &ipip_input_node, 99 | .register_callback = ipip_input_register, 100 | .trace_format = trace_ipip_format, 101 | }; 102 | 103 | GR_NODE_REGISTER(ipip_input_info); 104 | 105 | GR_DROP_REGISTER(ipip_input_no_tunnel); 106 | -------------------------------------------------------------------------------- /modules/ipip/datapath_out.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2024 Robin Jarry 3 | 4 | #include "ipip_priv.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | #include 19 | 20 | #include 21 | 22 | enum { 23 | IP_OUTPUT = 0, 24 | NO_TUNNEL, 25 | NO_HEADROOM, 26 | EDGE_COUNT, 27 | }; 28 | 29 | static uint16_t 30 | ipip_output_process(struct rte_graph *graph, struct rte_node *node, void **objs, uint16_t nb_objs) { 31 | struct ip_output_mbuf_data *ip_data; 32 | const struct iface_info_ipip *ipip; 33 | struct ip_local_mbuf_data tunnel; 34 | const struct rte_ipv4_hdr *inner; 35 | struct rte_ipv4_hdr *outer; 36 | const struct iface *iface; 37 | struct rte_mbuf *mbuf; 38 | rte_edge_t edge; 39 | 40 | for (uint16_t i = 0; i < nb_objs; i++) { 41 | mbuf = objs[i]; 42 | 43 | // Resolve the IPIP interface from the nexthop provided by ip_output. 44 | ip_data = ip_output_mbuf_data(mbuf); 45 | iface = iface_from_id(ip_data->nh->iface_id); 46 | if (iface == NULL || iface->type != GR_IFACE_TYPE_IPIP) { 47 | edge = NO_TUNNEL; 48 | goto next; 49 | } 50 | if (gr_mbuf_is_traced(mbuf)) { 51 | struct trace_ipip_data *t = gr_mbuf_trace_add(mbuf, node, sizeof(*t)); 52 | t->iface_id = iface->id; 53 | } 54 | ip_data->iface = iface; 55 | ipip = (const struct iface_info_ipip *)iface->info; 56 | 57 | // Encapsulate with another IPv4 header. 58 | inner = rte_pktmbuf_mtod(mbuf, const struct rte_ipv4_hdr *); 59 | tunnel.src = ipip->local; 60 | tunnel.dst = ipip->remote; 61 | tunnel.len = rte_be_to_cpu_16(inner->total_length); 62 | tunnel.vrf_id = iface->vrf_id; 63 | tunnel.proto = IPPROTO_IPIP; 64 | tunnel.ttl = IPV4_DEFAULT_TTL; 65 | outer = (struct rte_ipv4_hdr *)rte_pktmbuf_prepend(mbuf, sizeof(*outer)); 66 | if (unlikely(outer == NULL)) { 67 | edge = NO_HEADROOM; 68 | goto next; 69 | } 70 | ip_set_fields(outer, &tunnel); 71 | 72 | // Resolve nexthop for the encapsulated packet. 73 | ip_data->nh = fib4_lookup(iface->vrf_id, ipip->remote); 74 | edge = IP_OUTPUT; 75 | 76 | next: 77 | rte_node_enqueue_x1(graph, node, edge, mbuf); 78 | } 79 | 80 | return nb_objs; 81 | } 82 | 83 | static void ipip_output_register(void) { 84 | ip_output_register_interface_type(GR_IFACE_TYPE_IPIP, "ipip_output"); 85 | } 86 | 87 | static struct rte_node_register ipip_output_node = { 88 | .name = "ipip_output", 89 | 90 | .process = ipip_output_process, 91 | 92 | .nb_edges = EDGE_COUNT, 93 | .next_nodes = { 94 | [IP_OUTPUT] = "ip_output", 95 | [NO_TUNNEL] = "ipip_output_no_tunnel", 96 | [NO_HEADROOM] = "error_no_headroom", 97 | }, 98 | }; 99 | 100 | static struct gr_node_info ipip_output_info = { 101 | .node = &ipip_output_node, 102 | .register_callback = ipip_output_register, 103 | .trace_format = trace_ipip_format, 104 | }; 105 | 106 | GR_NODE_REGISTER(ipip_output_info); 107 | 108 | GR_DROP_REGISTER(ipip_output_no_tunnel); 109 | -------------------------------------------------------------------------------- /modules/ipip/gr_ipip.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2024 Robin Jarry 3 | 4 | #ifndef _GR_API_IPIP 5 | #define _GR_API_IPIP 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | // IPIP reconfig attributes 14 | #define GR_IPIP_SET_LOCAL GR_BIT64(32) 15 | #define GR_IPIP_SET_REMOTE GR_BIT64(33) 16 | 17 | // Info for GR_IFACE_TYPE_IPIP interfaces 18 | struct gr_iface_info_ipip { 19 | ip4_addr_t local; 20 | ip4_addr_t remote; 21 | }; 22 | 23 | static_assert(sizeof(struct gr_iface_info_ipip) <= MEMBER_SIZE(struct gr_iface, info)); 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /modules/ipip/ipip_priv.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2024 Robin Jarry 3 | 4 | #ifndef _IPIP_PRIV_H 5 | #define _IPIP_PRIV_H 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | #include 14 | #include 15 | 16 | struct __rte_aligned(alignof(void *)) iface_info_ipip { 17 | BASE(gr_iface_info_ipip); 18 | }; 19 | 20 | struct iface *ipip_get_iface(ip4_addr_t local, ip4_addr_t remote, uint16_t vrf_id); 21 | 22 | struct trace_ipip_data { 23 | uint16_t iface_id; 24 | }; 25 | 26 | int trace_ipip_format(char *buf, size_t len, const void *data, size_t data_len); 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /modules/ipip/meson.build: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Copyright (c) 2024 Robin Jarry 3 | 4 | inc += include_directories('.') 5 | src += files( 6 | 'control.c', 7 | 'datapath_in.c', 8 | 'datapath_out.c', 9 | ) 10 | 11 | api_headers += files('gr_ipip.h') 12 | cli_inc += include_directories('.') 13 | cli_src += files('cli.c') 14 | -------------------------------------------------------------------------------- /modules/l4/gr_l4.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2024 Christophe Fontaine 3 | 4 | #ifndef _GR_L4_H 5 | #define _GR_L4_H 6 | 7 | #include 8 | 9 | #include 10 | 11 | void l4_input_register_port(uint8_t proto, rte_be16_t port, const char *next_node); 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /modules/l4/l4_input_local.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2024 Christophe Fontaine 3 | 4 | #include "gr_l4.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | 17 | enum edges { 18 | MANAGEMENT = 0, 19 | BAD_PROTO, 20 | EDGE_COUNT, 21 | }; 22 | 23 | static rte_edge_t udp_edges[65536] = {MANAGEMENT}; 24 | 25 | void l4_input_register_port(uint8_t proto, rte_be16_t port, const char *next_node) { 26 | uint16_t p = rte_be_to_cpu_16(port); 27 | LOG(DEBUG, "l4_input_register_port: proto=%hhu port=%hu -> %s", proto, p, next_node); 28 | switch (proto) { 29 | case IPPROTO_UDP: 30 | if (udp_edges[port] != MANAGEMENT) 31 | ABORT("next node already registered for udp port=%hu", p); 32 | udp_edges[proto] = gr_node_attach_parent("ip_input_local", next_node); 33 | gr_node_attach_parent("ip6_input_local", next_node); 34 | break; 35 | default: 36 | ABORT("proto not supported %hhu", proto); 37 | } 38 | } 39 | 40 | static uint16_t l4_input_local_process( 41 | struct rte_graph *graph, 42 | struct rte_node *node, 43 | void **objs, 44 | uint16_t nb_objs 45 | ) { 46 | struct rte_udp_hdr *hdr; 47 | struct rte_mbuf *mbuf; 48 | rte_edge_t edge; 49 | uint8_t proto; 50 | 51 | for (uint16_t i = 0; i < nb_objs; i++) { 52 | mbuf = objs[i]; 53 | edge = BAD_PROTO; 54 | 55 | if (mbuf->packet_type & RTE_PTYPE_L3_IPV4) 56 | proto = ip_local_mbuf_data(mbuf)->proto; 57 | else if (mbuf->packet_type & RTE_PTYPE_L3_IPV6) 58 | proto = ip6_local_mbuf_data(mbuf)->proto; 59 | else 60 | goto next; 61 | 62 | if (proto != IPPROTO_UDP) { 63 | edge = MANAGEMENT; 64 | goto next; 65 | } 66 | 67 | hdr = rte_pktmbuf_mtod(mbuf, struct rte_udp_hdr *); 68 | edge = udp_edges[hdr->dst_port]; 69 | next: 70 | rte_node_enqueue_x1(graph, node, edge, mbuf); 71 | } 72 | return nb_objs; 73 | } 74 | 75 | static void l4_input_local_register(void) { 76 | ip_input_local_add_proto(IPPROTO_UDP, "l4_input_local"); 77 | ip_input_local_add_proto(IPPROTO_TCP, "l4_input_local"); 78 | ip6_input_local_add_proto(IPPROTO_UDP, "l4_input_local"); 79 | ip6_input_local_add_proto(IPPROTO_TCP, "l4_input_local"); 80 | } 81 | static struct rte_node_register input_node = { 82 | .name = "l4_input_local", 83 | .process = l4_input_local_process, 84 | .nb_edges = EDGE_COUNT, 85 | .next_nodes = { 86 | [MANAGEMENT] = "l4_loopback_output", 87 | [BAD_PROTO] = "l4_bad_proto", 88 | }, 89 | }; 90 | 91 | static struct gr_node_info info = { 92 | .node = &input_node, 93 | .register_callback = l4_input_local_register, 94 | }; 95 | 96 | GR_NODE_REGISTER(info); 97 | -------------------------------------------------------------------------------- /modules/l4/l4_loopback_output.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2024 Christophe Fontaine 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | 15 | enum edges { 16 | REDIRECT = 0, 17 | NO_IFACE, 18 | BAD_PROTO, 19 | NO_HEADROOM, 20 | EDGE_COUNT, 21 | }; 22 | 23 | static uint16_t l4_loopback_output_process( 24 | struct rte_graph *graph, 25 | struct rte_node *node, 26 | void **objs, 27 | uint16_t nb_objs 28 | ) { 29 | struct rte_mbuf *mbuf; 30 | struct mbuf_data *d; 31 | rte_edge_t edge; 32 | 33 | for (uint16_t i = 0; i < nb_objs; i++) { 34 | mbuf = objs[i]; 35 | edge = REDIRECT; 36 | 37 | d = mbuf_data(mbuf); 38 | d->iface = get_vrf_iface(d->iface->vrf_id); 39 | if (!d->iface) { 40 | edge = NO_IFACE; 41 | goto next; 42 | } 43 | 44 | if (mbuf->packet_type & RTE_PTYPE_L3_IPV4) { 45 | struct ip_local_mbuf_data *d = ip_local_mbuf_data(mbuf); 46 | struct rte_ipv4_hdr *ip; 47 | ip = (struct rte_ipv4_hdr *)rte_pktmbuf_prepend(mbuf, sizeof(*ip)); 48 | if (ip == NULL) { 49 | edge = NO_HEADROOM; 50 | goto next; 51 | } 52 | ip_set_fields(ip, d); 53 | } else if (mbuf->packet_type & RTE_PTYPE_L3_IPV6) { 54 | struct ip6_local_mbuf_data *d = ip6_local_mbuf_data(mbuf); 55 | struct rte_ipv6_hdr *ip; 56 | ip = (struct rte_ipv6_hdr *)rte_pktmbuf_prepend(mbuf, sizeof(*ip)); 57 | if (ip == NULL) { 58 | edge = NO_HEADROOM; 59 | goto next; 60 | } 61 | ip6_set_fields(ip, d->len, d->proto, &d->src, &d->dst); 62 | ip->hop_limits = d->hop_limit; 63 | } else { 64 | edge = BAD_PROTO; 65 | } 66 | 67 | next: 68 | if (gr_mbuf_is_traced(mbuf)) { 69 | gr_mbuf_trace_add(mbuf, node, 0); 70 | } 71 | rte_node_enqueue_x1(graph, node, edge, mbuf); 72 | } 73 | 74 | return nb_objs; 75 | } 76 | 77 | static struct rte_node_register l4_loopback_output_node = { 78 | .name = "l4_loopback_output", 79 | .process = l4_loopback_output_process, 80 | .nb_edges = EDGE_COUNT, 81 | .next_nodes = { 82 | [REDIRECT] = "loopback_output", 83 | [NO_IFACE] = "no_loop_iface", 84 | [BAD_PROTO] = "l4_bad_proto", 85 | [NO_HEADROOM] = "error_no_headroom", 86 | }, 87 | }; 88 | 89 | static struct gr_node_info info = { 90 | .node = &l4_loopback_output_node, 91 | }; 92 | 93 | GR_NODE_REGISTER(info); 94 | 95 | GR_DROP_REGISTER(no_loop_iface); 96 | GR_DROP_REGISTER(l4_bad_proto); 97 | -------------------------------------------------------------------------------- /modules/l4/meson.build: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Copyright (c) 2024 Christophe Fontaine 3 | 4 | src += files( 5 | 'l4_input_local.c', 6 | 'l4_loopback_output.c', 7 | ) 8 | inc += include_directories('.') 9 | -------------------------------------------------------------------------------- /modules/meson.build: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Copyright (c) 2023 Robin Jarry 3 | 4 | subdir('infra') 5 | subdir('ip') 6 | subdir('ip6') 7 | subdir('ipip') 8 | subdir('l4') 9 | subdir('srv6') 10 | -------------------------------------------------------------------------------- /modules/srv6/meson.build: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Copyright (c) 2025 Olivier Gournet 3 | 4 | inc += include_directories('.') 5 | src += files( 6 | 'control_local.c', 7 | 'control_headend.c', 8 | 'datapath_local.c', 9 | 'datapath_headend.c', 10 | ) 11 | 12 | api_headers += files('gr_srv6.h') 13 | cli_inc += include_directories('.') 14 | cli_src += files('cli.c') 15 | -------------------------------------------------------------------------------- /modules/srv6/srv6_priv.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (c) 2025 Olivier Gournet 3 | 4 | #ifndef _GR_SRV6_PRIV 5 | #define _GR_SRV6_PRIV 6 | 7 | #include "gr_srv6.h" 8 | 9 | // 10 | // srv6 data shared between control - sr6_localsid node 11 | // 12 | struct srv6_localsid_data { 13 | gr_srv6_behavior_t behavior; 14 | uint16_t out_vrf_id; 15 | uint8_t flags; 16 | }; 17 | 18 | struct srv6_localsid_data *srv6_localsid_get(const struct rte_ipv6_addr *lsid, uint16_t vrf_id); 19 | 20 | // 21 | // srv6 data shared between control - sr6_headend node 22 | // 23 | // XXX it is racy by design. do something before it goes into production 24 | // 25 | struct srv6_policy_data { 26 | // nexthops that resolves to this policy. uses gr_vec 27 | struct nexthop **nhlist; 28 | 29 | struct rte_ipv6_addr bsid; 30 | gr_srv6_encap_behavior_t encap; 31 | uint16_t weight; 32 | uint16_t n_seglist; 33 | struct rte_ipv6_addr seglist[]; 34 | }; 35 | 36 | struct srv6_policy_data **srv6_steer_get(const struct nexthop *nh); 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /smoke/_init_frr.sh: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Copyright (c) 2025 Maxime Leroy, Free Mobile 3 | 4 | test_frr=true 5 | 6 | . $(dirname $0)/_init.sh 7 | 8 | tap_index=0 9 | create_interface() { 10 | local p="$1" 11 | local mac="$2" 12 | 13 | ip link add v0-$p type veth peer name v1-$p 14 | grcli add interface port $p devargs net_tap$tap_index,iface=tap-$p,remote=v0-$p mac $mac 15 | 16 | local max_tries=5 17 | local count=0 18 | while vtysh -c "show interface $p" 2>&1 | grep -q "% Can't find interface"; do 19 | if [ "$count" -ge "$max_tries" ]; then 20 | echo "Interface $p not found after $max_tries attempts." 21 | exit 1 22 | fi 23 | sleep 1 24 | count=$((count + 1)) 25 | done 26 | 27 | tap_index=$((tap_index + 1)) 28 | } 29 | 30 | set_ip_address() { 31 | local p="$1" 32 | local ip_cidr="$2" 33 | local max_tries=5 34 | local count=0 35 | 36 | if echo "$ip_cidr" | grep -q ':'; then 37 | # IPv6 38 | local frr_ip="ipv6" 39 | local gr_ip="ip6" 40 | else 41 | # IPv4 42 | local frr_ip="ip" 43 | local gr_ip="ip" 44 | fi 45 | 46 | local grep_pattern="^${p}[[:space:]]\+${ip_cidr}$" 47 | 48 | vtysh <<-EOF 49 | configure terminal 50 | interface ${p} 51 | ${frr_ip} address ${ip_cidr} 52 | exit 53 | EOF 54 | 55 | while ! grcli show ${gr_ip} address | grep -q "$grep_pattern"; do 56 | if [ "$count" -ge "$max_tries" ]; then 57 | echo "IP address $ip_cidr not set after $max_tries attempts." 58 | exit 1 59 | fi 60 | sleep 1 61 | count=$((count + 1)) 62 | done 63 | } 64 | 65 | set_ip_route() { 66 | local prefix="$1" 67 | local next_hop="$2" 68 | local max_tries=5 69 | local count=0 70 | 71 | if echo "$prefix" | grep -q ':'; then 72 | # IPv6 73 | local frr_ip="ipv6" 74 | local gr_ip="ip6" 75 | else 76 | # IPv4 77 | local frr_ip="ip" 78 | local gr_ip="ip" 79 | fi 80 | 81 | local grep_pattern="^0[[:space:]]\+${prefix}[[:space:]]\+${next_hop}[[:space:]]" 82 | 83 | vtysh <<-EOF 84 | configure terminal 85 | ${frr_ip} route ${prefix} ${next_hop} 86 | exit 87 | EOF 88 | 89 | while ! grcli show ${gr_ip} route | grep -q "${grep_pattern}"; do 90 | if [ "$count" -ge "$max_tries" ]; then 91 | echo "Route ${prefix} via ${next_hop} not found after ${max_tries} attempts." 92 | exit 1 93 | fi 94 | sleep 1 95 | count=$((count + 1)) 96 | done 97 | } 98 | -------------------------------------------------------------------------------- /smoke/affinity_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # SPDX-License-Identifier: BSD-3-Clause 3 | # Copyright (c) 2025 Robin Jarry 4 | 5 | . $(dirname $0)/_init.sh 6 | 7 | # Start with a single datapath CPU. 8 | grcli set affinity cpus control 0 datapath 1 9 | grcli add interface port p0 devargs net_null0,no-rx=1 rxqs 2 10 | grcli add interface port p1 devargs net_null1,no-rx=1 rxqs 2 11 | grcli show affinity qmap 12 | 13 | # Ensure that trying to manually move rxqs to CPUs outside of the reserved 14 | # datapath affinity mask returns an error. 15 | grcli set affinity qmap p0 rxq 0 cpu 666 && fail "qmap to CPU 666 should fail" 16 | grcli set affinity qmap p0 rxq 0 cpu 2 && fail "qmap to CPU 2 should fail" 17 | 18 | grcli set affinity cpus datapath 1,2,3 19 | grcli show affinity qmap 20 | 21 | grcli set affinity qmap p0 rxq 0 cpu 1 22 | grcli set affinity qmap p0 rxq 1 cpu 1 23 | grcli set affinity qmap p1 rxq 0 cpu 2 24 | grcli set affinity qmap p1 rxq 1 cpu 2 25 | grcli show affinity qmap 26 | 27 | grcli set affinity cpus datapath 2,3 28 | grcli show affinity qmap 29 | -------------------------------------------------------------------------------- /smoke/config_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # SPDX-License-Identifier: BSD-3-Clause 3 | # Copyright (c) 2024 Robin Jarry 4 | 5 | . $(dirname $0)/_init.sh 6 | 7 | grcli add interface port p0 devargs net_null0,no-rx=1 8 | grcli add interface port p1 devargs net_null1,no-rx=1 9 | grcli add ip address 10.0.0.1/24 iface p0 10 | grcli add ip address 10.1.0.1/24 iface p1 11 | grcli add ip route 0.0.0.0/0 via 10.0.0.2 12 | grcli add ip6 address 2345::1/24 iface p0 13 | grcli add ip6 address 2346::1/24 iface p1 14 | grcli add ip6 route ::/0 via 2345::2 15 | grcli set interface port p0 rxqs 2 16 | grcli set interface port p1 rxqs 2 17 | grcli show interface 18 | grcli show ip route 19 | grcli show ip nexthop 20 | grcli show ip6 route 21 | grcli show ip6 nexthop 22 | grcli show graph full 23 | grcli show stats software 24 | grcli show stats hardware 25 | -------------------------------------------------------------------------------- /smoke/graph_svg_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # SPDX-License-Identifier: BSD-3-Clause 3 | # Copyright (c) 2024 Robin Jarry 4 | 5 | svg_to_edges() { 6 | grep -A1 'class="edge"' | sed -En 's,^(.+)$,\1,p' | 7 | sed 's/->/ -> /' | LC_ALL=C sort -u 8 | } 9 | 10 | . $(dirname $0)/_init.sh 11 | 12 | grcli add interface port foo devargs net_null0,no-rx=1 13 | 14 | command -v dot || fail "graphviz is not installed" 15 | 16 | # compare runtime graph with the image stored in git 17 | svg_to_edges < docs/graph.svg > $tmp/edges_git 18 | grcli show graph | dot -Tsvg | svg_to_edges > $tmp/edges_runtime 19 | diff -u $tmp/edges_git $tmp/edges_runtime || fail "docs/graph.svg is not up to date" 20 | -------------------------------------------------------------------------------- /smoke/ip6_add_del_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # SPDX-License-Identifier: BSD-3-Clause 3 | # Copyright (c) 2025 Christophe Fontaine 4 | 5 | . $(dirname $0)/_init.sh 6 | 7 | p0=${run_id}0 8 | 9 | grcli add interface port $p0 devargs net_tap0,iface=$p0 mac f0:0d:ac:dc:00:00 10 | grcli del interface $p0 11 | grcli add interface port $p0 devargs net_tap0,iface=$p0 mac f0:0d:ac:dc:00:00 12 | 13 | grcli add ip6 address 2001::1/64 iface $p0 14 | grcli show ip6 address 15 | grcli del ip6 address 2001::1/64 iface $p0 16 | grcli show ip6 address 17 | grcli add ip6 address 2001::1/64 iface $p0 18 | grcli show ip6 address 19 | -------------------------------------------------------------------------------- /smoke/ip6_builtin_icmp_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # SPDX-License-Identifier: BSD-3-Clause 3 | # Copyright (c) 2025 Olivier Gournet 4 | 5 | . $(dirname $0)/_init.sh 6 | 7 | p0=${run_id}0 8 | p1=${run_id}1 9 | 10 | grcli add interface port $p0 devargs net_tap0,iface=$p0 mac f0:0d:ac:dc:13:00 11 | grcli add interface port $p1 devargs net_tap1,iface=$p1 mac f0:0d:ac:dc:13:01 12 | grcli add ip6 address fd00:ba4:0::1/64 iface $p0 13 | grcli add ip6 address fd00:ba4:1::1/64 iface $p1 14 | 15 | for n in 0 1; do 16 | p=$run_id$n 17 | netns_add $p 18 | ip link set $p netns $p 19 | ip -n $p link set $p address ba:d0:ca:cd:00:0$n 20 | ip -n $p link set $p up 21 | ip -n $p addr add fd00:ba4:$n::2/64 dev $p 22 | ip -n $p route add fd00:ba4::/62 via fd00:ba4:$n::1 dev $p 23 | ip -n $p addr show 24 | done 25 | 26 | sleep 3 # wait for DAD 27 | 28 | grcli ping fd00:ba4:0::2 count 10 delay 100 29 | grcli ping fd00:ba4:1::2 count 3 delay 10 30 | 31 | # Expect this test to fail 32 | grcli ping fd00:baa::1 count 1 && fail "ping to unknown route succeeded" 33 | grcli ping fd00:ba4:1::3 count 1 && fail "ping to non-existent host succeeded" 34 | 35 | grcli traceroute fd00:ba4:1::2 36 | -------------------------------------------------------------------------------- /smoke/ip6_forward_frr_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # SPDX-License-Identifier: BSD-3-Clause 3 | # Copyright (c) 2025 Maxime Leroy, Free Mobile 4 | 5 | . $(dirname $0)/_init_frr.sh 6 | 7 | p1=${run_id}1 8 | p2=${run_id}2 9 | 10 | create_interface $p1 d2:f0:0c:ba:a4:11 11 | create_interface $p2 d2:f0:0c:ba:a4:12 12 | 13 | for n in 1 2; do 14 | p=$run_id$n 15 | netns_add n-$p 16 | ip link set v1-$p netns n-$p 17 | ip -n n-$p link set v1-$p address d2:ad:ca:ca:a4:1$n 18 | ip -n n-$p link set v1-$p up 19 | ip -n n-$p link set lo up 20 | ip -n n-$p addr add fd00:ba4:$n::2/64 dev v1-$p 21 | ip -n n-$p addr add fd00:f00:$n::2/64 dev lo 22 | ip -n n-$p route add default via fd00:ba4:$n::1 23 | ip -n n-$p addr show 24 | done 25 | 26 | set_ip_address $p1 fd00:ba4:1::1/64 27 | set_ip_address $p2 fd00:ba4:2::1/64 28 | set_ip_route fd00:f00:1::/64 fd00:ba4:1::2 29 | set_ip_route fd00:f00:2::/64 fd00:ba4:2::2 30 | 31 | sleep 3 # wait for DAD 32 | 33 | ip netns exec n-$p1 ping6 -i0.01 -c3 -n fe80::d2f0:cff:feba:a411 34 | ip netns exec n-$p2 ping6 -i0.01 -c3 -n fe80::d2f0:cff:feba:a412 35 | ip netns exec n-$p1 ping6 -i0.01 -c3 -n fd00:f00:2::2 36 | ip netns exec n-$p2 ping6 -i0.01 -c3 -n fd00:f00:1::2 37 | ip netns exec n-$p1 ping6 -i0.01 -c3 -n fd00:ba4:2::2 38 | ip netns exec n-$p2 ping6 -i0.01 -c3 -n fd00:ba4:1::2 39 | ip netns exec n-$p1 ping6 -i0.01 -c3 -n fd00:ba4:1::1 40 | ip netns exec n-$p2 ping6 -i0.01 -c3 -n fd00:ba4:2::1 41 | ip netns exec n-$p1 traceroute -N1 -n fd00:ba4:2::2 42 | ip netns exec n-$p2 traceroute -N1 -n fd00:ba4:1::2 43 | ip netns exec n-$p1 traceroute -N1 -n fd00:f00:2::2 44 | ip netns exec n-$p2 traceroute -N1 -n fd00:f00:1::2 45 | -------------------------------------------------------------------------------- /smoke/ip6_forward_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # SPDX-License-Identifier: BSD-3-Clause 3 | # Copyright (c) 2024 Robin Jarry 4 | 5 | . $(dirname $0)/_init.sh 6 | 7 | p1=${run_id}1 8 | p2=${run_id}2 9 | 10 | grcli add interface port $p1 devargs net_tap0,iface=$p1 mac d2:f0:0c:ba:a4:11 11 | grcli add interface port $p2 devargs net_tap1,iface=$p2 mac d2:f0:0c:ba:a4:12 12 | grcli add ip6 address fd00:ba4:1::1/64 iface $p1 13 | grcli add ip6 address fd00:ba4:2::1/64 iface $p2 14 | grcli add ip6 route fd00:f00:1::/64 via fd00:ba4:1::2 15 | grcli add ip6 route fd00:f00:2::/64 via fd00:ba4:2::2 16 | 17 | for n in 1 2; do 18 | p=$run_id$n 19 | netns_add $p 20 | ip link set $p netns $p 21 | ip -n $p link set $p address d2:ad:ca:ca:a4:1$n 22 | ip -n $p link set $p up 23 | ip -n $p link set lo up 24 | ip -n $p addr add fd00:ba4:$n::2/64 dev $p 25 | ip -n $p addr add fd00:f00:$n::2/64 dev lo 26 | ip -n $p route add default via fd00:ba4:$n::1 27 | ip -n $p addr show 28 | done 29 | 30 | sleep 3 # wait for DAD 31 | 32 | ip netns exec $p1 ping6 -i0.01 -c3 -n fe80::d2f0:cff:feba:a411 33 | ip netns exec $p2 ping6 -i0.01 -c3 -n fe80::d2f0:cff:feba:a412 34 | ip netns exec $p1 ping6 -i0.01 -c3 -n fd00:f00:2::2 35 | ip netns exec $p2 ping6 -i0.01 -c3 -n fd00:f00:1::2 36 | ip netns exec $p1 ping6 -i0.01 -c3 -n fd00:ba4:2::2 37 | ip netns exec $p2 ping6 -i0.01 -c3 -n fd00:ba4:1::2 38 | ip netns exec $p1 ping6 -i0.01 -c3 -n fd00:ba4:1::1 39 | ip netns exec $p2 ping6 -i0.01 -c3 -n fd00:ba4:2::1 40 | ip netns exec $p1 traceroute -N1 -n fd00:ba4:2::2 41 | ip netns exec $p2 traceroute -N1 -n fd00:ba4:1::2 42 | ip netns exec $p1 traceroute -N1 -n fd00:f00:2::2 43 | ip netns exec $p2 traceroute -N1 -n fd00:f00:1::2 44 | -------------------------------------------------------------------------------- /smoke/ip6_rs_ra_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # SPDX-License-Identifier: BSD-3-Clause 3 | # Copyright (c) 2025 Christophe Fontaine 4 | 5 | . $(dirname $0)/_init.sh 6 | 7 | p1=${run_id}1 8 | 9 | grcli add interface port $p1 devargs net_tap0,iface=$p1 mac d2:f0:0c:ba:a4:11 10 | grcli add ip6 address fd00:ba4:1::1/64 iface $p1 11 | 12 | for n in 1; do 13 | p=$run_id$n 14 | netns_add $p 15 | ip link set $p netns $p 16 | ip -n $p link set $p address d2:ad:ca:ca:a4:1$n 17 | ip -n $p link set $p up 18 | ip -n $p addr add fd00:ba4:$n::2/64 dev $p 19 | ip -n $p addr show 20 | done 21 | 22 | sleep 3 # wait for DAD 23 | 24 | ip netns exec $p1 rdisc6 -n $p1 25 | -------------------------------------------------------------------------------- /smoke/ip6_same_peer_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # SPDX-License-Identifier: BSD-3-Clause 3 | # Copyright (c) 2025 Christophe Fontaine 4 | 5 | . $(dirname $0)/_init.sh 6 | 7 | p1=${run_id}1 8 | p2=${run_id}2 9 | 10 | grcli add interface port $p1 devargs net_tap0,iface=$p1 mac d2:f0:0c:ba:a4:11 11 | grcli add interface port $p2 devargs net_tap1,iface=$p2 mac d2:f0:0c:ba:a4:12 12 | 13 | grcli add ip6 address fd00:ba4:1::1/64 iface $p1 14 | grcli add ip6 address fd00:ba4:2::1/64 iface $p2 15 | 16 | for n in 1 2; do 17 | p=$run_id$n 18 | netns_add $p 19 | ip link set $p netns $p 20 | ip -n $p link set $p address d2:ad:ca:ca:a4:1 21 | ip -n $p link set $p up 22 | ip -n $p addr add fd00:ba4:$n::2/64 dev $p 23 | # ip -n $p addr add fe80::beef:2/64 dev $p 24 | ip -n $p route add default via fd00:ba4:$n::1 25 | ip -n $p addr show 26 | done 27 | 28 | sleep 3 # wait for DAD 29 | 30 | ip netns exec $p1 ping6 -i0.01 -c3 -n fe80::d2f0:cff:feba:a411 31 | ip netns exec $p2 ping6 -i0.01 -c3 -n fe80::d2f0:cff:feba:a412 32 | ip netns exec $p1 ping6 -i0.01 -c3 -n fe80::d2f0:cff:feba:a412 && fail "Unexpected answer from foreign link local address" 33 | ip netns exec $p1 ping6 -i0.01 -c3 -n fd00:ba4:2::2 34 | ip netns exec $p2 ping6 -i0.01 -c3 -n fd00:ba4:1::2 35 | ip netns exec $p1 ping6 -i0.01 -c3 -n fd00:ba4:1::1 36 | ip netns exec $p2 ping6 -i0.01 -c3 -n fd00:ba4:2::1 37 | ip netns exec $p1 traceroute -N1 -n fd00:ba4:2::2 38 | ip netns exec $p2 traceroute -N1 -n fd00:ba4:1::2 39 | -------------------------------------------------------------------------------- /smoke/ip_add_del_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # SPDX-License-Identifier: BSD-3-Clause 3 | # Copyright (c) 2025 Christophe Fontaine 4 | 5 | . $(dirname $0)/_init.sh 6 | 7 | p0=${run_id}0 8 | 9 | grcli add interface port $p0 devargs net_tap0,iface=$p0 mac f0:0d:ac:dc:00:00 10 | 11 | grcli add ip address 172.16.0.1/24 iface $p0 12 | grcli show ip address 13 | grcli del ip address 172.16.0.1/24 iface $p0 14 | grcli show ip address 15 | grcli add ip address 172.16.0.1/24 iface $p0 16 | grcli show ip address 17 | -------------------------------------------------------------------------------- /smoke/ip_builtin_icmp_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # SPDX-License-Identifier: BSD-3-Clause 3 | # Copyright (c) 2024 Christophe Fontaine 4 | 5 | . $(dirname $0)/_init.sh 6 | 7 | p0=${run_id}0 8 | p1=${run_id}1 9 | 10 | grcli add interface port $p0 devargs net_tap0,iface=$p0 mac f0:0d:ac:dc:00:00 11 | grcli add interface port $p1 devargs net_tap1,iface=$p1 mac f0:0d:ac:dc:00:01 12 | grcli add ip address 172.16.2.1/24 iface $p0 13 | grcli add ip address 172.16.0.1/24 iface $p0 14 | grcli add ip address 172.16.1.1/24 iface $p1 15 | 16 | for n in 0 1; do 17 | p=$run_id$n 18 | netns_add $p 19 | ip link set $p netns $p 20 | ip -n $p link set $p address ba:d0:ca:ca:00:0$n 21 | ip -n $p link set $p up 22 | ip -n $p addr add 172.16.$n.2/24 dev $p 23 | ip -n $p route add default via 172.16.$n.1 24 | ip -n $p addr show 25 | done 26 | 27 | set -m 28 | 29 | grcli ping 172.16.0.2 count 10 delay 100 & 30 | grcli ping 172.16.1.2 count 3 delay 10 31 | 32 | fg 33 | 34 | # Expect this test to fail 35 | grcli ping 1.1.1.1 count 1 && fail "ping to unknown route succeeded" 36 | grcli ping 172.16.1.3 count 1 && fail "ping to non-existent host succeeded" 37 | 38 | grcli traceroute 172.16.0.2 39 | -------------------------------------------------------------------------------- /smoke/ip_forward_frr_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # SPDX-License-Identifier: BSD-3-Clause 3 | # Copyright (c) 2025 Maxime Leroy, Free Mobile 4 | 5 | . $(dirname $0)/_init_frr.sh 6 | 7 | p0=${run_id}0 8 | p1=${run_id}1 9 | 10 | create_interface $p0 f0:0d:ac:dc:00:00 11 | create_interface $p1 f0:0d:ac:dc:00:01 12 | 13 | for n in 0 1; do 14 | p=$run_id$n 15 | netns_add n-$p 16 | ip link set v1-$p netns n-$p 17 | ip -n n-$p link set v1-$p address ba:d0:ca:ca:00:0$n 18 | ip -n n-$p link set v1-$p up 19 | ip -n n-$p link set lo up 20 | ip -n n-$p addr add 172.16.$n.2/24 dev v1-$p 21 | ip -n n-$p addr add 16.$n.0.1/16 dev lo 22 | ip -n n-$p route add default via 172.16.$n.1 23 | ip -n n-$p addr show 24 | done 25 | 26 | set_ip_address $p0 172.16.0.1/24 27 | set_ip_address $p1 172.16.1.1/24 28 | set_ip_route 16.0.0.0/16 172.16.0.2 29 | set_ip_route 16.1.0.0/16 172.16.1.2 30 | 31 | ip netns exec n-$p0 ping -i0.01 -c3 -n 16.1.0.1 32 | ip netns exec n-$p1 ping -i0.01 -c3 -n 16.0.0.1 33 | ip netns exec n-$p0 ping -i0.01 -c3 -n 172.16.1.2 34 | ip netns exec n-$p1 ping -i0.01 -c3 -n 172.16.0.2 35 | ip netns exec n-$p0 ping -i0.01 -c3 -n 172.16.0.1 36 | ip netns exec n-$p1 ping -i0.01 -c3 -n 172.16.1.1 37 | ip netns exec n-$p0 traceroute -N1 -n 16.1.0.1 38 | ip netns exec n-$p1 traceroute -N1 -n 16.0.0.1 39 | ip netns exec n-$p0 traceroute -N1 -n 172.16.1.2 40 | ip netns exec n-$p1 traceroute -N1 -n 172.16.0.2 41 | -------------------------------------------------------------------------------- /smoke/ip_forward_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # SPDX-License-Identifier: BSD-3-Clause 3 | # Copyright (c) 2024 Robin Jarry 4 | 5 | . $(dirname $0)/_init.sh 6 | 7 | p0=${run_id}0 8 | p1=${run_id}1 9 | 10 | grcli add interface port $p0 devargs net_tap0,iface=$p0 mac f0:0d:ac:dc:00:00 11 | grcli add interface port $p1 devargs net_tap1,iface=$p1 mac f0:0d:ac:dc:00:01 12 | grcli add ip address 172.16.0.1/24 iface $p0 13 | grcli add ip address 172.16.1.1/24 iface $p1 14 | grcli add ip route 16.0.0.0/16 via 172.16.0.2 15 | grcli add ip route 16.1.0.0/16 via 172.16.1.2 16 | 17 | 18 | for n in 0 1; do 19 | p=$run_id$n 20 | netns_add $p 21 | ip link set $p netns $p 22 | ip -n $p link set $p address ba:d0:ca:ca:00:0$n 23 | ip -n $p link set $p up 24 | ip -n $p link set lo up 25 | ip -n $p addr add 172.16.$n.2/24 dev $p 26 | ip -n $p addr add 16.$n.0.1/16 dev lo 27 | ip -n $p route add default via 172.16.$n.1 28 | ip -n $p addr show 29 | done 30 | 31 | ip netns exec $p0 ping -i0.01 -c3 -n 16.1.0.1 32 | ip netns exec $p1 ping -i0.01 -c3 -n 16.0.0.1 33 | ip netns exec $p0 ping -i0.01 -c3 -n 172.16.1.2 34 | ip netns exec $p1 ping -i0.01 -c3 -n 172.16.0.2 35 | ip netns exec $p0 ping -i0.01 -c3 -n 172.16.0.1 36 | ip netns exec $p1 ping -i0.01 -c3 -n 172.16.1.1 37 | ip netns exec $p0 traceroute -N1 -n 16.1.0.1 38 | ip netns exec $p1 traceroute -N1 -n 16.0.0.1 39 | ip netns exec $p0 traceroute -N1 -n 172.16.1.2 40 | ip netns exec $p1 traceroute -N1 -n 172.16.0.2 41 | -------------------------------------------------------------------------------- /smoke/ipip_encap_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # SPDX-License-Identifier: BSD-3-Clause 3 | # Copyright (c) 2024 Robin Jarry 4 | 5 | . $(dirname $0)/_init.sh 6 | 7 | p0=${run_id}0 8 | p1=${run_id}1 9 | iptun=${run_id}tun1 10 | 11 | grcli add interface port $p0 devargs net_tap0,iface=$p0 mac f0:0d:ac:dc:00:01 12 | grcli add interface port $p1 devargs net_tap1,iface=$p1 mac f0:0d:ac:dc:00:02 13 | grcli add ip address 10.99.0.1/24 iface $p0 14 | grcli add ip address 172.16.1.1/24 iface $p1 15 | grcli add interface ipip $iptun local 172.16.1.1 remote 172.16.1.2 16 | grcli add ip address 10.98.0.1/24 iface $iptun 17 | 18 | netns_add $p0 19 | ip link set $p0 netns $p0 20 | ip -n $p0 link set $p0 address ba:d0:ca:ca:00:00 21 | ip -n $p0 link set $p0 up 22 | ip -n $p0 addr add 10.99.0.2/24 dev $p0 23 | ip -n $p0 route add default via 10.99.0.1 24 | ip -n $p0 addr show 25 | 26 | netns_add $p1 27 | ip link set $p1 netns $p1 28 | ip -n $p1 link set $p1 address ba:d0:ca:ca:00:01 29 | ip -n $p1 link set $p1 up 30 | ip -n $p1 addr add 172.16.1.2/24 dev $p1 31 | ip -n $p1 tunnel add $iptun mode ipip local 172.16.1.2 remote 172.16.1.1 32 | ip -n $p1 link set $iptun up 33 | ip -n $p1 addr add 10.98.0.2/24 dev $iptun 34 | ip -n $p1 route add default via 10.98.0.1 35 | ip -n $p1 addr show 36 | 37 | ip netns exec $p0 ping -i0.01 -c3 -n 10.98.0.2 38 | ip netns exec $p1 ping -i0.01 -c3 -n 10.99.0.2 39 | -------------------------------------------------------------------------------- /smoke/nexthop_ageing_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # SPDX-License-Identifier: BSD-3-Clause 3 | # Copyright (c) 2025 Robin Jarry 4 | 5 | . $(dirname $0)/_init.sh 6 | 7 | check_nexthop() { 8 | local ip="$1" 9 | local expect_reacheable="$2" 10 | local timeout=5 11 | for i in $(seq 1 $timeout); do 12 | grcli show ip nexthop | grep -qE "$ip.+reachable"; 13 | local result=$? 14 | 15 | if [ "$expect_reacheable" = "true" ] && [ "$result" -eq 0 ]; then 16 | return 0 17 | fi 18 | if [ "$expect_reacheable" = "false" ] && [ "$result" -ne 0 ]; then 19 | return 0 20 | fi 21 | 22 | sleep 1 23 | done 24 | 25 | return 1 26 | } 27 | 28 | p0=${run_id}0 29 | p1=${run_id}1 30 | 31 | grcli set config nexthop lifetime 2 unreachable 1 ucast-probes 1 bcast-probes 1 32 | grcli add interface port $p0 devargs net_tap0,iface=$p0 mac f0:0d:ac:dc:00:00 33 | grcli add interface port $p1 devargs net_tap1,iface=$p1 mac f0:0d:ac:dc:00:01 34 | grcli add ip address 172.16.0.1/24 iface $p0 35 | grcli add ip address 172.16.1.1/24 iface $p1 36 | 37 | for n in 0 1; do 38 | p=$run_id$n 39 | netns_add $p 40 | ip link set $p netns $p 41 | ip -n $p link set $p address ba:d0:ca:ca:00:0$n 42 | ip -n $p link set $p up 43 | ip -n $p link set lo up 44 | ip -n $p addr add 172.16.$n.2/24 dev $p 45 | ip -n $p route add default via 172.16.$n.1 46 | ip -n $p addr show 47 | done 48 | 49 | ip netns exec $p0 ping -i0.01 -c3 -n 172.16.1.2 50 | ip netns exec $p1 ping -i0.01 -c3 -n 172.16.0.2 51 | 52 | grcli show ip nexthop 53 | # let nexthops lifetime expire and wait for ARP probes to be sent 54 | sleep 3 55 | 56 | grcli </dev/null 2>&1; then 28 | test_frr=true 29 | fi 30 | 31 | for script in $here/*_test.sh; do 32 | name=$(basename $script) 33 | case "$name" in 34 | *_frr_test.sh) 35 | [ "$test_frr" = true ] || continue 36 | ;; 37 | esac 38 | 39 | printf "%s ... " "$name" 40 | start=$(date +%s) 41 | res=OK 42 | 43 | { 44 | echo "=====================================================" 45 | echo "+ $script $builddir" 46 | if ! "$script" "$builddir"; then 47 | res=FAILED 48 | fi 49 | for core in /tmp/grout-core.*.*; do 50 | [ -s "$core" ] || continue 51 | binary=$(file -b "$core" | sed -En "s/.*, execfn: '([^']+)',.*/\\1/p") 52 | [ -x "$binary" ] || continue 53 | gdb -ex 'info threads' \ 54 | -ex 'thread apply all bt full' \ 55 | -ex 'quit' \ 56 | "$binary" -c "$core" || true 57 | rm -f "$core" 58 | done 59 | end=$(date +%s) 60 | duration=$(date -d "@$((end - start))" "+%Mm%Ss") 61 | echo "-----------------------------------------------------" 62 | printf '%s %s (%s)\n' "$name" "$res" "$duration" 63 | echo "-----------------------------------------------------" 64 | } >$log 2>&1 65 | 66 | if [ "$res" = FAILED ]; then 67 | result=1 68 | echo 69 | cat $log 70 | rm -f $log 71 | else 72 | printf 'OK (%s)\n' "$duration" 73 | fi 74 | done 75 | 76 | exit $result 77 | -------------------------------------------------------------------------------- /smoke/srv6_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # SPDX-License-Identifier: BSD-3-Clause 3 | # Copyright (c) 2025 Olivier Gournet 4 | 5 | . $(dirname $0)/_init.sh 6 | 7 | p0=${run_id}0 8 | p1=${run_id}1 9 | 10 | netns_add $p0 11 | netns_add $p1 12 | 13 | # setup ports and connected 14 | grcli add interface port $p0 devargs net_tap0,iface=$p0 mac d2:f0:0c:ba:a5:10 15 | grcli add interface port $p1 devargs net_tap1,iface=$p1 mac d2:f0:0c:ba:a5:11 16 | grcli add ip6 address fd00:101::1/64 iface $p0 17 | grcli add ip6 address fd00:102::1/64 iface $p1 18 | grcli add ip address 192.168.61.1/24 iface $p0 19 | grcli add ip address 192.168.62.1/24 iface $p1 20 | ip link set $p0 netns $p0 21 | ip -n $p0 link set $p0 address d2:ad:ca:fe:b4:10 22 | ip -n $p0 link set lo up 23 | ip -n $p0 link set $p0 up 24 | ip -n $p0 addr add fd00:101::2/64 dev $p0 25 | ip -n $p0 addr add 192.168.61.2/24 dev $p0 26 | ip link set $p1 netns $p1 27 | ip -n $p1 link set $p1 address d2:ad:ca:fe:b4:11 28 | ip -n $p1 link set lo up 29 | ip -n $p1 link set $p1 up 30 | ip -n $p1 addr add fd00:102::2/64 dev $p1 31 | ip -n $p1 addr add 192.168.62.2/24 dev $p1 32 | 33 | sleep 3 34 | 35 | # 36 | # network layout: 37 | # (client) p0(netns) <--> p0 p1 <---> p1(netns) (public: 192.168.60.1/24 on p0) 38 | # ipv4 ---------------| srv6 |-- ipv4 39 | # 40 | # test case: 41 | # - (1) send ipv4 ping from p0 42 | # - (2) grout encap in srv6, send to sid fd00:202::2 43 | # - (3) linux p1 decap it 44 | # - (4) reply to ping 45 | # - (5) linux p1 reencap in srv6, send to grout sid fd00:202::100, 46 | # - (6) grout decap it, reply back in ipv4 to p0 47 | # 48 | 49 | # only linux's p1 will see srv6 50 | ip netns exec $p1 sysctl -w net.ipv6.conf.$p1.seg6_enabled=1 51 | ip netns exec $p1 sysctl -w net.ipv6.conf.$p1.forwarding=1 52 | 53 | # (1) send ipv4 to grout 54 | ip -n $p0 route add default via 192.168.61.1 dev $p0 55 | 56 | # (2) 57 | grcli add sr policy bsid fd00:202::200 seglist fd00:202::2 58 | grcli add sr steer 192.168.0.0/16 bsid fd00:202::200 59 | grcli add ip6 route fd00:202::/64 via fd00:102::2 60 | 61 | # (3) 62 | ip -n $p1 -6 route add fd00:202::2 encap seg6local action End.DX4 nh4 192.168.60.1 count dev $p1 63 | 64 | # (4) 192.168.60.0/24 is our 'public' network 65 | ip -n $p1 addr add 192.168.60.1/24 dev $p1 66 | 67 | # (5) 68 | ip -n $p1 route add 192.168.61.0/24 encap seg6 mode encap segs fd00:202::100 dev $p1 69 | ip -n $p1 -6 route add fd00:202::/64 via fd00:102::1 dev $p1 70 | 71 | # (6) 72 | grcli add sr localsid fd00:202::100 behavior end.dt4 73 | 74 | # test 75 | ip netns exec $p0 ping -c 3 192.168.60.1 76 | -------------------------------------------------------------------------------- /smoke/vlan_forward_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # SPDX-License-Identifier: BSD-3-Clause 3 | # Copyright (c) 2024 Robin Jarry 4 | 5 | . $(dirname $0)/_init.sh 6 | 7 | p0=${run_id}0 8 | p1=${run_id}1 9 | v0=$p0.42 10 | v1=$p1.43 11 | 12 | grcli add interface port $p0 devargs net_tap0,iface=$p0 mac f0:0d:ac:dc:00:00 13 | grcli add interface port $p1 devargs net_tap1,iface=$p1 mac f0:0d:ac:dc:00:01 14 | grcli add interface vlan $v0 parent $p0 vlan_id 42 15 | grcli add interface vlan $v1 parent $p1 vlan_id 43 16 | grcli add ip address 172.16.0.1/24 iface $v0 17 | grcli add ip address 172.16.1.1/24 iface $v1 18 | 19 | for n in 0 1; do 20 | p=$run_id$n 21 | v=$p.$((n+42)) 22 | netns_add $p 23 | ip link set $p netns $p 24 | ip -n $p link add $v link $p type vlan id $((n+42)) 25 | ip -n $p link set $p address ba:d0:ca:ca:00:0$n 26 | ip -n $p link set $p up 27 | ip -n $p link set $v up 28 | ip -n $p addr add 172.16.$n.2/24 dev $v 29 | ip -n $p route add default via 172.16.$n.1 30 | ip -n $p addr show 31 | done 32 | 33 | ip netns exec $p0 ping -i0.01 -c3 -n 172.16.1.2 34 | ip netns exec $p1 ping -i0.01 -c3 -n 172.16.0.2 35 | -------------------------------------------------------------------------------- /smoke/vrf_forward_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # SPDX-License-Identifier: BSD-3-Clause 3 | # Copyright (c) 2024 Robin Jarry 4 | 5 | . $(dirname $0)/_init.sh 6 | 7 | p0=${run_id}0 8 | p1=${run_id}1 9 | p2=${run_id}2 10 | p3=${run_id}3 11 | 12 | grcli add interface port $p0 devargs net_tap0,iface=$p0 vrf 1 mac f0:0d:ac:dc:01:00 13 | grcli add interface port $p1 devargs net_tap1,iface=$p1 vrf 1 mac f0:0d:ac:dc:01:01 14 | grcli add interface port $p2 devargs net_tap2,iface=$p2 vrf 2 mac f0:0d:ac:dc:02:00 15 | grcli add interface port $p3 devargs net_tap3,iface=$p3 vrf 2 mac f0:0d:ac:dc:02:01 16 | grcli add ip address 172.16.0.1/24 iface $p0 17 | grcli add ip address 172.16.1.1/24 iface $p1 18 | grcli add ip address 172.16.0.1/24 iface $p2 19 | grcli add ip address 172.16.1.1/24 iface $p3 20 | 21 | for n in 0 1; do 22 | p=$run_id$n 23 | netns_add $p 24 | ip link set $p netns $p 25 | ip -n $p link set $p address ba:d0:ca:ca:01:0$n 26 | ip -n $p link set $p up 27 | ip -n $p addr add 172.16.$((n % 2)).2/24 dev $p 28 | ip -n $p route add default via 172.16.$((n % 2)).1 29 | ip -n $p addr show 30 | done 31 | ip netns exec $p0 ping -i0.01 -c3 -n 172.16.1.2 32 | 33 | for n in 2 3; do 34 | p=$run_id$n 35 | netns_add $p 36 | ip link set $p netns $p 37 | ip -n $p link set $p address ba:d0:ca:ca:02:0$n 38 | ip -n $p link set $p up 39 | ip -n $p addr add 172.16.$((n % 2)).2/24 dev $p 40 | ip -n $p route add default via 172.16.$((n % 2)).1 41 | ip -n $p addr show 42 | done 43 | ip netns exec $p2 ping -i0.01 -c3 -n 172.16.1.2 44 | -------------------------------------------------------------------------------- /subprojects/dpdk.wrap: -------------------------------------------------------------------------------- 1 | [wrap-git] 2 | url = https://github.com/DPDK/dpdk-stable 3 | revision = v24.11.1 4 | depth = 1 5 | 6 | [provide] 7 | dependency_names = libdpdk 8 | -------------------------------------------------------------------------------- /subprojects/ecoli.wrap: -------------------------------------------------------------------------------- 1 | [wrap-git] 2 | url = https://github.com/rjarry/libecoli 3 | revision = v0.5.0 4 | depth = 1 5 | 6 | [provide] 7 | dependency_names = libecoli 8 | -------------------------------------------------------------------------------- /subprojects/frr.wrap: -------------------------------------------------------------------------------- 1 | [wrap-git] 2 | url = https://github.com/FRRouting/frr 3 | revision = frr-10.3 4 | depth = 1 5 | diff_files = 6 | frr/lib-clippy-pointer-offsets-are-signed.patch, 7 | frr/meson-add-dependency-definition.patch 8 | 9 | [provide] 10 | dependency_names = frr 11 | -------------------------------------------------------------------------------- /subprojects/packagefiles/frr/lib-clippy-pointer-offsets-are-signed.patch: -------------------------------------------------------------------------------- 1 | From 1419dd0a0bb31d7a783ca3da371526ec8ac647a8 Mon Sep 17 00:00:00 2001 2 | From: David Lamparter 3 | Date: Sun, 11 May 2025 16:38:34 +0200 4 | Subject: [PATCH] lib/clippy: pointer offsets are signed 5 | 6 | Fedora 42 has some new GCC/ld combination that has negative offsets from 7 | the .note.FRR to the xref pointers. (This is completely fine, those 8 | offsets are supposed to be signed.) Clippy decoded them as unsigned, 9 | resulting in off-by-2^64 offset values (which Python cheerfully 10 | processes, due to its builtin "large integer" support... in C code it 11 | would've just wrapped in an uint64_t and made no difference...) 12 | 13 | Read the values as signed like they should be. 14 | 15 | Signed-off-by: David Lamparter 16 | --- 17 | python/xrelfo.py | 4 ++-- 18 | 1 file changed, 2 insertions(+), 2 deletions(-) 19 | 20 | diff --git a/python/xrelfo.py b/python/xrelfo.py 21 | index 5f7616f25093..e70c33e62cb6 100644 22 | --- a/python/xrelfo.py 23 | +++ b/python/xrelfo.py 24 | @@ -385,11 +385,11 @@ def load_elf(self, filename, orig_filename): 25 | endian = ">" if edf._elffile.bigendian else "<" 26 | mem = edf._elffile[note] 27 | if edf._elffile.elfclass == 64: 28 | - start, end = struct.unpack(endian + "QQ", mem) 29 | + start, end = struct.unpack(endian + "qq", mem) 30 | start += note.start 31 | end += note.start + 8 32 | else: 33 | - start, end = struct.unpack(endian + "II", mem) 34 | + start, end = struct.unpack(endian + "ii", mem) 35 | start += note.start 36 | end += note.start + 4 37 | 38 | --------------------------------------------------------------------------------