samples: net: capture: Add a way to capture some cooked data
For demonstration purposes add a way to capture some arbitrary data and send it in cooked mode for analysis. Signed-off-by: Jukka Rissanen <jukka.rissanen@nordicsemi.no>
This commit is contained in:
parent
76290b052f
commit
4ae75194aa
4 changed files with 361 additions and 0 deletions
20
samples/net/capture/Kconfig
Normal file
20
samples/net/capture/Kconfig
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# Private config options for capture sample app
|
||||||
|
|
||||||
|
# Copyright (c) 2024 Intel Corporation
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
mainmenu "Networking capture sample application"
|
||||||
|
|
||||||
|
config NET_SAMPLE_TUNNEL_PEER_ADDR
|
||||||
|
string "Remote IP address of the tunnel interface"
|
||||||
|
depends on NET_L2_IPIP
|
||||||
|
help
|
||||||
|
Use overlay-tunnel.conf to setup the tunnel support.
|
||||||
|
|
||||||
|
config NET_SAMPLE_TUNNEL_MY_ADDR
|
||||||
|
string "My address for tunnel interface"
|
||||||
|
depends on NET_L2_IPIP
|
||||||
|
help
|
||||||
|
The value depends on your network setup.
|
||||||
|
|
||||||
|
source "Kconfig.zephyr"
|
14
samples/net/capture/overlay-tunnel.conf
Normal file
14
samples/net/capture/overlay-tunnel.conf
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
CONFIG_NET_L2_VIRTUAL=y
|
||||||
|
CONFIG_NET_L2_VIRTUAL_MGMT=y
|
||||||
|
CONFIG_NET_L2_IPIP=y
|
||||||
|
|
||||||
|
CONFIG_NET_IF_MAX_IPV6_COUNT=4
|
||||||
|
CONFIG_NET_IF_MAX_IPV4_COUNT=4
|
||||||
|
|
||||||
|
# Use this for IPv6 over IPv4
|
||||||
|
CONFIG_NET_SAMPLE_TUNNEL_MY_ADDR="2001:db8:200::1"
|
||||||
|
CONFIG_NET_SAMPLE_TUNNEL_PEER_ADDR="2001:db8:200::2"
|
||||||
|
|
||||||
|
# Use this for IPv4 over IPv4
|
||||||
|
#CONFIG_NET_SAMPLE_TUNNEL_MY_ADDR="198.51.100.1"
|
||||||
|
#CONFIG_NET_SAMPLE_TUNNEL_PEER_ADDR="198.51.100.2"
|
|
@ -25,5 +25,7 @@ CONFIG_NET_CONFIG_SETTINGS=y
|
||||||
CONFIG_NET_CONFIG_NEED_IPV6=y
|
CONFIG_NET_CONFIG_NEED_IPV6=y
|
||||||
CONFIG_NET_CONFIG_NEED_IPV4=y
|
CONFIG_NET_CONFIG_NEED_IPV4=y
|
||||||
CONFIG_NET_CONFIG_MY_IPV6_ADDR="2001:db8::1"
|
CONFIG_NET_CONFIG_MY_IPV6_ADDR="2001:db8::1"
|
||||||
|
CONFIG_NET_CONFIG_PEER_IPV6_ADDR="2001:db8::2"
|
||||||
CONFIG_NET_CONFIG_MY_IPV4_ADDR="192.0.2.1"
|
CONFIG_NET_CONFIG_MY_IPV4_ADDR="192.0.2.1"
|
||||||
|
CONFIG_NET_CONFIG_PEER_IPV4_ADDR="192.0.2.2"
|
||||||
CONFIG_NET_CONFIG_MY_IPV4_NETMASK="255.255.255.0"
|
CONFIG_NET_CONFIG_MY_IPV4_NETMASK="255.255.255.0"
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2021 Intel Corporation.
|
* Copyright (c) 2021 Intel Corporation.
|
||||||
|
* Copyright (c) 2024 Nordic Semiconductor
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
@ -7,10 +8,334 @@
|
||||||
#include <zephyr/logging/log.h>
|
#include <zephyr/logging/log.h>
|
||||||
LOG_MODULE_REGISTER(net_capture_sample, LOG_LEVEL_DBG);
|
LOG_MODULE_REGISTER(net_capture_sample, LOG_LEVEL_DBG);
|
||||||
|
|
||||||
|
#include <zephyr/posix/net/if_arp.h>
|
||||||
#include <zephyr/kernel.h>
|
#include <zephyr/kernel.h>
|
||||||
|
#include <zephyr/shell/shell.h>
|
||||||
|
#include <zephyr/net/capture.h>
|
||||||
|
#include <zephyr/net/ethernet.h>
|
||||||
|
#include <zephyr/net/virtual_mgmt.h>
|
||||||
|
|
||||||
|
#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 <ifindex>\" 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)
|
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");
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue