/* * Copyright (c) 2021 Intel Corporation. * Copyright (c) 2024 Nordic Semiconductor * * SPDX-License-Identifier: Apache-2.0 */ #include LOG_MODULE_REGISTER(net_capture_sample, LOG_LEVEL_DBG); #include #include #include #include #include #include #if defined(CONFIG_NET_CAPTURE_COOKED_MODE) #define COOKED_MODE_INTERFACE_NAME CONFIG_NET_CAPTURE_COOKED_MODE_INTERFACE_NAME #else #define COOKED_MODE_INTERFACE_NAME "" #endif static bool started; uint16_t link_types_to_monitor[] = { NET_ETH_PTYPE_CAN, NET_ETH_PTYPE_HDLC, }; struct data_to_send { struct net_capture_cooked *ctx; size_t len; const char *data; enum net_capture_packet_type type; uint16_t eth_p_type; }; #define DATA(_ctx, _data, _type, _ptype) { \ .ctx = _ctx, \ .len = sizeof(_data), \ .data = _data, \ .type = _type, \ .eth_p_type = _ptype \ } static const char can_data[] = { 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, }; static struct net_capture_cooked ctx_can = { .hatype = ARPHRD_CAN, .halen = 0, .addr = { 0 } }; static const char ppp_send_lcp_conf_req_data[] = { 0xff, 0x7d, 0x23, 0xc0, 0x21, 0x7d, 0x21, 0x7d, 0x21, 0x7d, 0x20, 0x7d, 0x24, 0xd1, 0xb5, }; static const char ppp_recv_lcp_conf_req_data[] = { 0xff, 0x03, 0xc0, 0x21, 0x01, 0x01, 0x00, 0x16, 0x02, 0x06, 0x00, 0x00, 0x00, 0x00, 0x03, 0x04, 0xc0, 0x23, 0x05, 0x06, 0xe4, 0xdd, 0x30, 0x57, 0x07, 0x02, 0x0c, 0x57, }; static const char ppp_send_lcp_conf_rej_data[] = { 0xff, 0x7d, 0x23, 0xc0, 0x21, 0x7d, 0x24, 0x7d, 0x21, 0x7d, 0x20, 0x7d, 0x32, 0x7d, 0x22, 0x7d, 0x26, 0x7d, 0x20, 0x7d, 0x20, 0x7d, 0x20, 0x7d, 0x20, 0x7d, 0x25, 0x7d, 0x26, 0x7d, 0x20, 0x38, 0xea, 0x74, 0x7d, 0x27, 0x7d, 0x22, 0x8c, 0xa3, }; static struct net_capture_cooked ctx_ppp = { .hatype = ARPHRD_PPP, .halen = 6, .addr = { 0x1, 0x2, 0x3, 0x4, 0x5, 0x6 } }; /* We just construct some demo packets and send them out */ static const struct data_to_send data[] = { DATA(&ctx_can, can_data, NET_CAPTURE_OUTGOING, NET_ETH_PTYPE_CAN), DATA(&ctx_ppp, ppp_send_lcp_conf_req_data, NET_CAPTURE_OUTGOING, NET_ETH_PTYPE_HDLC), DATA(&ctx_ppp, ppp_recv_lcp_conf_req_data, NET_CAPTURE_HOST, NET_ETH_PTYPE_HDLC), DATA(&ctx_ppp, ppp_send_lcp_conf_rej_data, NET_CAPTURE_OUTGOING, NET_ETH_PTYPE_HDLC), }; static int cmd_sample_send(const struct shell *sh, size_t argc, char *argv[]) { if (!IS_ENABLED(CONFIG_NET_CAPTURE_COOKED_MODE)) { shell_fprintf(sh, SHELL_NORMAL, "Enable %s to use the sample shell.\n", "CONFIG_NET_CAPTURE_COOKED_MODE"); return 0; } if (!started) { if (sh != NULL) { shell_fprintf(sh, SHELL_WARNING, "%s", "Capturing not enabled, cannot send data.\n"); } return 0; } ARRAY_FOR_EACH_PTR(data, ptr) { net_capture_data(ptr->ctx, ptr->data, ptr->len, ptr->type, ptr->eth_p_type); } return 0; } SHELL_STATIC_SUBCMD_SET_CREATE(sample_commands, SHELL_CMD(send, NULL, "Send example data\n", cmd_sample_send), SHELL_SUBCMD_SET_END ); SHELL_CMD_REGISTER(sample, &sample_commands, "Sample application commands", NULL); static int init_app(void) { /* What this sample does: * - Create a tunnel that runs on top of the Ethernet interface * - Start to capture data from cooked mode capture interface * - Take the cooked mode interface up * * All of the above could be done manually from net-shell with * these commands: * * net capture setup 192.0.2.2 2001:db8:200::1 2001:db8:200::2 * net capture enable 2 * net iface up 2 * * Explanation what those commands do: * * The "net capture setup" creates a tunnel. The tunnel is IPv6 * tunnel and our inner end point address is 2001:db8:200::1 * and the inner peer end point address is 2001:db8:200::2. In the * tests, the tunnel can be created in Linux host with this command * * net-setup.sh -c zeth-tunnel.conf * * The net-setup.sh command is found in net-tools Zephyr project. * In host side, the tunnel interface is called zeth-ip6ip where IPv6 * packets are run in a IPv4 tunnel. * * The network interfaces in this sample application are: * * Interface any (0x808ab3c) (Dummy) [1] * ================================ * Virtual interfaces attached to this : 2 * Device : NET_ANY (0x80849a4) * * Interface cooked (0x808ac94) (Virtual) [2] * ================================== * Virtual name : Cooked mode capture * Attached : 1 (Dummy / 0x808ab3c) * Device : NET_COOKED (0x808497c) * * Interface eth0 (0x808adec) (Ethernet) [3] * =================================== * Virtual interfaces attached to this : 4 * Device : zeth0 (0x80849b8) * IPv6 unicast addresses (max 4): * fe80::5eff:fe00:53e6 autoconf preferred infinite * 2001:db8::1 manual preferred infinite * IPv4 unicast addresses (max 2): * 192.0.2.1/255.255.255.0 overridable preferred infinite * * Interface net0 (0x808af44) (Virtual) [4] * ================================== * Virtual name : Capture tunnel * Attached : 3 (Ethernet / 0x808adec) * Device : IP_TUNNEL0 (0x8084990) * IPv6 unicast addresses (max 4): * 2001:db8:200::1 manual preferred infinite * fe80::efed:6dff:fef2:b1df autoconf preferred infinite * fe80::56da:1eff:fe5e:bc02 autoconf preferred infinite * * The 192.0.2.2 is the address of the outer end point of the host that * terminates the tunnel. Zephyr uses this address to select the internal * interface to use for the tunnel. In this example it is interface 3. * * The interface 2 is the interface that runs on top of interface 1. The * cooked capture packets are written by the capture API to sink interface 1. * The packets propagate to interface 2 because it is linked to first interface. * The "net capture enable 2" command will cause the packets sent to interface 2 * to be written to capture interface 4, which in turn capsulates the packets and * tunnels them to peer via the Ethernet interface 3. * * The above IP addresses might change if you change the addresses in * overlay-tunnel.conf file. * * If the cooked mode capture is enabled (CONFIG_NET_CAPTURE_COOKED_MODE=y), * then we setup the capture automatically to the correct network interface. * User is then able to send sample network packets in cooked mode and monitor * the captured packets in the host zeth-ip6ip network interface. Use "sample send" * command to do that. * * You can use "net-capture.py -i zeth-ip6ip -c" command to capture the cooked * packets in host side. The net-capture.py tool is found in net-tools package. */ const char remote[] = CONFIG_NET_CONFIG_PEER_IPV4_ADDR; const char local[] = CONFIG_NET_SAMPLE_TUNNEL_MY_ADDR; const char peer[] = CONFIG_NET_SAMPLE_TUNNEL_PEER_ADDR; const struct device *capture_dev; int ret; ret = net_capture_setup(remote, local, peer, &capture_dev); if (ret < 0) { LOG_ERR("Capture cannot be setup (%d)", ret); return -ENOEXEC; } if (IS_ENABLED(CONFIG_NET_CAPTURE_COOKED_MODE)) { /* If we are running in cooked mode, start to capture the packets * from cooked mode interface. */ struct virtual_interface_req_params params = { 0 }; struct virtual_interface_link_types link_types; int ifindex; ifindex = net_if_get_by_name(COOKED_MODE_INTERFACE_NAME); if (ifindex < 0) { LOG_ERR("Interface \"%s\" not found.", COOKED_MODE_INTERFACE_NAME); return -ENOENT; } ret = net_capture_enable(capture_dev, net_if_get_by_index(ifindex)); if (ret < 0) { LOG_ERR("Cannot enable capture to interface %d (%d)", ifindex, ret); return ret; } /* Setup the cooked interface to capture these types of packets */ memcpy(&link_types.type, &link_types_to_monitor, MIN(sizeof(link_types.type), sizeof(link_types_to_monitor))); link_types.count = MIN(ARRAY_SIZE(link_types.type), ARRAY_SIZE(link_types_to_monitor)); params.family = AF_UNSPEC; memcpy(¶ms.link_types, &link_types, sizeof(struct virtual_interface_link_types)); ret = net_mgmt(NET_REQUEST_VIRTUAL_INTERFACE_SET_LINK_TYPE, net_if_get_by_index(ifindex), ¶ms, sizeof(struct virtual_interface_req_params)); if (ret < 0 && ret != -ENOTSUP) { LOG_ERR("Cannot set interface %d link types (%d)", ifindex, ret); return ret; } /* Now the capture device and the tunnel interface is setup, * we just need to bring up the actual interface we want to capture * as it is down by default. */ ret = net_if_up(net_if_get_by_index(ifindex)); if (ret < 0) { LOG_ERR("Cannot take up interface %d (%d)", ifindex, ret); return ret; } LOG_INF("Type \"sample send\" to send dummy capture data to tunnel."); } else { LOG_INF("Please enable capture manually from net-shell"); LOG_INF("Use \"net capture enable \" command to start " "capturing desired network interface."); } return 0; } #define EVENT_MASK (NET_EVENT_CAPTURE_STARTED | NET_EVENT_CAPTURE_STOPPED) static void event_handler(struct net_mgmt_event_callback *cb, uint32_t mgmt_event, struct net_if *iface) { ARG_UNUSED(iface); ARG_UNUSED(cb); if ((mgmt_event & EVENT_MASK) != mgmt_event) { return; } if (mgmt_event == NET_EVENT_CAPTURE_STARTED) { started = true; } if (mgmt_event == NET_EVENT_CAPTURE_STOPPED) { started = false; } } int main(void) { static struct net_mgmt_event_callback mgmt_cb; struct net_if *iface; uint32_t event; int ret; LOG_INF("Starting network capture sample"); net_mgmt_init_event_callback(&mgmt_cb, event_handler, EVENT_MASK); net_mgmt_add_event_callback(&mgmt_cb); ret = init_app(); if (ret < 0) { LOG_ERR("Cannot start the application."); return ret; } while (1) { ret = net_mgmt_event_wait(EVENT_MASK, &event, &iface, NULL, NULL, K_FOREVER); if (ret < 0) { continue; } LOG_INF("Capturing %s on interface %d", event == NET_EVENT_CAPTURE_STARTED ? "started" : "stopped", net_if_get_by_iface(iface)); } return 0; }