net: capture: Add Linux cooked mode capture support
Add support for capturing arbitrary data via the cooked mode (sll) capture API. The actual packet capture is done using net_capture_data() function, the packet capture infrastructure does not need any changes. Signed-off-by: Jukka Rissanen <jukka.rissanen@nordicsemi.no>
This commit is contained in:
parent
b8f7942377
commit
46cb5c0bd1
|
@ -211,6 +211,90 @@ static inline void net_capture_pkt(struct net_if *iface, struct net_pkt *pkt)
|
|||
}
|
||||
#endif
|
||||
|
||||
/** The type and direction of the captured data. */
|
||||
enum net_capture_packet_type {
|
||||
NET_CAPTURE_HOST, /**< Packet was sent to us by somebody else */
|
||||
NET_CAPTURE_BROADCAST, /**< Packet was broadcast by somebody else */
|
||||
NET_CAPTURE_MULTICAST, /**< Packet was multicast, but not broadcast, by somebody else */
|
||||
NET_CAPTURE_OTHERHOST, /**< Packet was sent by somebody else to somebody else */
|
||||
NET_CAPTURE_OUTGOING, /**< Packet was sent by us */
|
||||
};
|
||||
|
||||
#define NET_CAPTURE_LL_ADDRLEN 8 /** Maximum length of a link-layer address */
|
||||
|
||||
/** The context information for cooked mode capture */
|
||||
struct net_capture_cooked {
|
||||
/** Link-layer address type */
|
||||
uint16_t hatype;
|
||||
/** Link-layer address length */
|
||||
uint16_t halen;
|
||||
/** Link-layer address */
|
||||
uint8_t addr[NET_CAPTURE_LL_ADDRLEN];
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Initialize cooked mode capture context.
|
||||
*
|
||||
* @param cooked Cooked context struct allocated by user.
|
||||
* @param hatype Link-layer address type
|
||||
* @param halen Link-layer address length (maximum is 8 bytes)
|
||||
* @param addr Link-layer address
|
||||
*
|
||||
* @return 0 if ok, <0 if context initialization failed
|
||||
*/
|
||||
#if defined(CONFIG_NET_CAPTURE_COOKED_MODE)
|
||||
int net_capture_cooked_setup(struct net_capture_cooked *ctx,
|
||||
uint16_t hatype,
|
||||
uint16_t halen,
|
||||
uint8_t *addr);
|
||||
#else
|
||||
static inline int net_capture_cooked_setup(struct net_capture_cooked *ctx,
|
||||
uint16_t hatype,
|
||||
uint16_t halen,
|
||||
uint8_t *addr)
|
||||
{
|
||||
ARG_UNUSED(ctx);
|
||||
ARG_UNUSED(hatype);
|
||||
ARG_UNUSED(halen);
|
||||
ARG_UNUSED(addr);
|
||||
|
||||
return -ENOTSUP;
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Capture arbitrary data from source that does not have an interface.
|
||||
* This can be used if you do not have a network interface that
|
||||
* you want to capture from. For example low level modem device
|
||||
* below PPP containing HDLC frames, CANBUS data or Bluetooth packets etc.
|
||||
* The data given to this function should only contain full link
|
||||
* layer packets so that packet boundary is not lost.
|
||||
*
|
||||
* @param ctx Cooked mode capture context.
|
||||
* @param buf Data to capture.
|
||||
* @param len Length of the data.
|
||||
* @param type The direction and type of the packet (did we sent it etc).
|
||||
* @param ptype Protocol type id. These are the ETH_P_* types set in ethernet.h
|
||||
*/
|
||||
#if defined(CONFIG_NET_CAPTURE_COOKED_MODE)
|
||||
void net_capture_data(struct net_capture_cooked *ctx,
|
||||
const uint8_t *data, size_t len,
|
||||
enum net_capture_packet_type type,
|
||||
uint16_t ptype);
|
||||
#else
|
||||
static inline void net_capture_data(struct net_capture_cooked *ctx,
|
||||
const uint8_t *data, size_t len,
|
||||
enum net_capture_packet_type type,
|
||||
uint16_t ptype)
|
||||
{
|
||||
ARG_UNUSED(ctx);
|
||||
ARG_UNUSED(data);
|
||||
ARG_UNUSED(len);
|
||||
ARG_UNUSED(type);
|
||||
ARG_UNUSED(ptype);
|
||||
}
|
||||
#endif
|
||||
|
||||
struct net_capture_info {
|
||||
const struct device *capture_dev;
|
||||
struct net_if *capture_iface;
|
||||
|
|
|
@ -306,6 +306,11 @@ struct net_pkt {
|
|||
};
|
||||
#endif /* CONFIG_NET_OFFLOAD */
|
||||
|
||||
#if defined(CONFIG_NET_CAPTURE_COOKED_MODE)
|
||||
/* Tell the capture api that this is a captured packet */
|
||||
uint8_t cooked_mode_pkt : 1;
|
||||
#endif /* CONFIG_NET_CAPTURE_COOKED_MODE */
|
||||
|
||||
/* @endcond */
|
||||
};
|
||||
|
||||
|
@ -892,6 +897,31 @@ static inline void net_pkt_set_priority(struct net_pkt *pkt,
|
|||
pkt->priority = priority;
|
||||
}
|
||||
|
||||
#if defined(CONFIG_NET_CAPTURE_COOKED_MODE)
|
||||
static inline bool net_pkt_is_cooked_mode(struct net_pkt *pkt)
|
||||
{
|
||||
return pkt->cooked_mode_pkt;
|
||||
}
|
||||
|
||||
static inline void net_pkt_set_cooked_mode(struct net_pkt *pkt, bool value)
|
||||
{
|
||||
pkt->cooked_mode_pkt = value;
|
||||
}
|
||||
#else
|
||||
static inline bool net_pkt_is_cooked_mode(struct net_pkt *pkt)
|
||||
{
|
||||
ARG_UNUSED(pkt);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline void net_pkt_set_cooked_mode(struct net_pkt *pkt, bool value)
|
||||
{
|
||||
ARG_UNUSED(pkt);
|
||||
ARG_UNUSED(value);
|
||||
}
|
||||
#endif /* CONFIG_NET_CAPTURE_COOKED_MODE */
|
||||
|
||||
#if defined(CONFIG_NET_VLAN)
|
||||
static inline uint16_t net_pkt_vlan_tag(struct net_pkt *pkt)
|
||||
{
|
||||
|
|
|
@ -54,6 +54,14 @@ enum virtual_interface_caps {
|
|||
enum virtual_interface_config_type {
|
||||
VIRTUAL_INTERFACE_CONFIG_TYPE_PEER_ADDRESS,
|
||||
VIRTUAL_INTERFACE_CONFIG_TYPE_MTU,
|
||||
VIRTUAL_INTERFACE_CONFIG_TYPE_LINK_TYPE,
|
||||
};
|
||||
|
||||
struct virtual_interface_link_types {
|
||||
int count;
|
||||
uint16_t type[COND_CODE_1(CONFIG_NET_CAPTURE_COOKED_MODE,
|
||||
(CONFIG_NET_CAPTURE_COOKED_MODE_MAX_LINK_TYPES),
|
||||
(1))];
|
||||
};
|
||||
|
||||
struct virtual_interface_config {
|
||||
|
@ -62,6 +70,7 @@ struct virtual_interface_config {
|
|||
struct in_addr peer4addr;
|
||||
struct in6_addr peer6addr;
|
||||
int mtu;
|
||||
struct virtual_interface_link_types link_types;
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -43,6 +43,7 @@ struct virtual_interface_req_params {
|
|||
struct in_addr peer4addr;
|
||||
struct in6_addr peer6addr;
|
||||
int mtu;
|
||||
struct virtual_interface_link_types link_types;
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -51,6 +52,8 @@ enum net_request_virtual_interface_cmd {
|
|||
NET_REQUEST_VIRTUAL_INTERFACE_CMD_GET_PEER_ADDR,
|
||||
NET_REQUEST_VIRTUAL_INTERFACE_CMD_SET_MTU,
|
||||
NET_REQUEST_VIRTUAL_INTERFACE_CMD_GET_MTU,
|
||||
NET_REQUEST_VIRTUAL_INTERFACE_CMD_SET_LINK_TYPE,
|
||||
NET_REQUEST_VIRTUAL_INTERFACE_CMD_GET_LINK_TYPE,
|
||||
};
|
||||
|
||||
#define NET_REQUEST_VIRTUAL_INTERFACE_SET_PEER_ADDRESS \
|
||||
|
@ -65,6 +68,12 @@ NET_MGMT_DEFINE_REQUEST_HANDLER(NET_REQUEST_VIRTUAL_INTERFACE_SET_PEER_ADDRESS);
|
|||
|
||||
NET_MGMT_DEFINE_REQUEST_HANDLER(NET_REQUEST_VIRTUAL_INTERFACE_SET_MTU);
|
||||
|
||||
#define NET_REQUEST_VIRTUAL_INTERFACE_SET_LINK_TYPE \
|
||||
(_NET_VIRTUAL_INTERFACE_BASE | \
|
||||
NET_REQUEST_VIRTUAL_INTERFACE_CMD_SET_LINK_TYPE)
|
||||
|
||||
NET_MGMT_DEFINE_REQUEST_HANDLER(NET_REQUEST_VIRTUAL_INTERFACE_SET_LINK_TYPE);
|
||||
|
||||
#define NET_REQUEST_VIRTUAL_INTERFACE_GET_PEER_ADDRESS \
|
||||
(_NET_VIRTUAL_INTERFACE_BASE | \
|
||||
NET_REQUEST_VIRTUAL_INTERFACE_CMD_GET_PEER_ADDR)
|
||||
|
@ -77,6 +86,12 @@ NET_MGMT_DEFINE_REQUEST_HANDLER(NET_REQUEST_VIRTUAL_INTERFACE_GET_PEER_ADDRESS);
|
|||
|
||||
NET_MGMT_DEFINE_REQUEST_HANDLER(NET_REQUEST_VIRTUAL_INTERFACE_GET_MTU);
|
||||
|
||||
#define NET_REQUEST_VIRTUAL_INTERFACE_GET_LINK_TYPE \
|
||||
(_NET_VIRTUAL_INTERFACE_BASE | \
|
||||
NET_REQUEST_VIRTUAL_INTERFACE_CMD_GET_LINK_TYPE)
|
||||
|
||||
NET_MGMT_DEFINE_REQUEST_HANDLER(NET_REQUEST_VIRTUAL_INTERFACE_GET_LINK_TYPE);
|
||||
|
||||
struct net_if;
|
||||
|
||||
/** @endcond */
|
||||
|
|
|
@ -54,6 +54,13 @@ static int virtual_interface_set_config(uint32_t mgmt_request,
|
|||
config.family = params->family;
|
||||
config.mtu = params->mtu;
|
||||
type = VIRTUAL_INTERFACE_CONFIG_TYPE_MTU;
|
||||
|
||||
} else if (mgmt_request == NET_REQUEST_VIRTUAL_INTERFACE_SET_LINK_TYPE) {
|
||||
/* We can update the link types even if the interface is up */
|
||||
config.family = params->family;
|
||||
memcpy(&config.link_types, ¶ms->link_types,
|
||||
sizeof(config.link_types));
|
||||
type = VIRTUAL_INTERFACE_CONFIG_TYPE_LINK_TYPE;
|
||||
} else {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
@ -67,6 +74,9 @@ NET_MGMT_REGISTER_REQUEST_HANDLER(NET_REQUEST_VIRTUAL_INTERFACE_SET_PEER_ADDRESS
|
|||
NET_MGMT_REGISTER_REQUEST_HANDLER(NET_REQUEST_VIRTUAL_INTERFACE_SET_MTU,
|
||||
virtual_interface_set_config);
|
||||
|
||||
NET_MGMT_REGISTER_REQUEST_HANDLER(NET_REQUEST_VIRTUAL_INTERFACE_SET_LINK_TYPE,
|
||||
virtual_interface_set_config);
|
||||
|
||||
static int virtual_interface_get_config(uint32_t mgmt_request,
|
||||
struct net_if *iface,
|
||||
void *data, size_t len)
|
||||
|
@ -112,6 +122,17 @@ static int virtual_interface_get_config(uint32_t mgmt_request,
|
|||
}
|
||||
|
||||
params->mtu = config.mtu;
|
||||
|
||||
} else if (mgmt_request == NET_REQUEST_VIRTUAL_INTERFACE_GET_LINK_TYPE) {
|
||||
type = VIRTUAL_INTERFACE_CONFIG_TYPE_LINK_TYPE;
|
||||
|
||||
ret = api->get_config(iface, type, &config);
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
memcpy(¶ms->link_types, &config.link_types,
|
||||
sizeof(params->link_types));
|
||||
} else {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
@ -124,3 +145,6 @@ NET_MGMT_REGISTER_REQUEST_HANDLER(NET_REQUEST_VIRTUAL_INTERFACE_GET_PEER_ADDRESS
|
|||
|
||||
NET_MGMT_REGISTER_REQUEST_HANDLER(NET_REQUEST_VIRTUAL_INTERFACE_GET_MTU,
|
||||
virtual_interface_get_config);
|
||||
|
||||
NET_MGMT_REGISTER_REQUEST_HANDLER(NET_REQUEST_VIRTUAL_INTERFACE_GET_LINK_TYPE,
|
||||
virtual_interface_get_config);
|
||||
|
|
|
@ -3,4 +3,8 @@
|
|||
zephyr_include_directories(.)
|
||||
zephyr_include_directories(${ZEPHYR_BASE}/subsys/net/ip)
|
||||
|
||||
zephyr_sources(capture.c)
|
||||
zephyr_library_sources(capture.c)
|
||||
|
||||
if(CONFIG_NET_CAPTURE_COOKED_MODE)
|
||||
zephyr_library_sources(cooked.c)
|
||||
endif()
|
||||
|
|
|
@ -46,6 +46,54 @@ config NET_CAPTURE_DEVICE_COUNT
|
|||
if one needs to send captured data to multiple different devices,
|
||||
then you need to increase the value.
|
||||
|
||||
config NET_CAPTURE_COOKED_MODE
|
||||
bool "Capture non-IP packets a.k.a cooked (SLL) mode [EXPERIMENTAL]"
|
||||
select NET_PSEUDO_IFACE
|
||||
select NET_L2_DUMMY
|
||||
select NET_L2_VIRTUAL
|
||||
select EXPERIMENTAL
|
||||
help
|
||||
This enables application to capture packets in so called
|
||||
Linux cooked mode (sll). Here a synthetic link layer header
|
||||
is used instead of real network link header.
|
||||
|
||||
choice NET_CAPTURE_COOKED_MODE_SLL_VERSION
|
||||
prompt "SLL version to use"
|
||||
depends on NET_CAPTURE_COOKED_MODE
|
||||
default NET_CAPTURE_COOKED_MODE_SLLV1
|
||||
help
|
||||
What SLL header version to use.
|
||||
|
||||
config NET_CAPTURE_COOKED_MODE_SLLV1
|
||||
bool "SLL version 1"
|
||||
depends on NET_CAPTURE_COOKED_MODE
|
||||
help
|
||||
Use SLL version 1 (header is 16 bytes)
|
||||
|
||||
config NET_CAPTURE_COOKED_MODE_SLLV2
|
||||
bool "SLL version 2"
|
||||
depends on NET_CAPTURE_COOKED_MODE
|
||||
help
|
||||
Use SLL version 2 (header is 20 bytes)
|
||||
|
||||
endchoice
|
||||
|
||||
config NET_CAPTURE_COOKED_MODE_INTERFACE_NAME
|
||||
string "Name of the cooked mode network interface"
|
||||
default "cooked"
|
||||
depends on NET_CAPTURE_COOKED_MODE
|
||||
help
|
||||
This sets the name of the cooked mode capture network interface.
|
||||
|
||||
config NET_CAPTURE_COOKED_MODE_MAX_LINK_TYPES
|
||||
int "How many link types (ETH_P_*) to capture at the same time"
|
||||
default 2
|
||||
range 1 64
|
||||
depends on NET_CAPTURE_COOKED_MODE
|
||||
help
|
||||
This defines how many ETH_P_* link type values can be captured
|
||||
at the same time in cooked mode.
|
||||
|
||||
module = NET_CAPTURE
|
||||
module-dep = NET_LOG
|
||||
module-str = Log level for network capture API
|
||||
|
|
|
@ -508,6 +508,7 @@ void net_capture_pkt(struct net_if *iface, struct net_pkt *pkt)
|
|||
struct k_mem_slab *orig_slab;
|
||||
struct net_pkt *captured;
|
||||
sys_snode_t *sn, *sns;
|
||||
bool skip_clone = false;
|
||||
|
||||
/* We must prevent to capture network packet that is already captured
|
||||
* in order to avoid recursion.
|
||||
|
@ -528,6 +529,17 @@ void net_capture_pkt(struct net_if *iface, struct net_pkt *pkt)
|
|||
continue;
|
||||
}
|
||||
|
||||
/* If the packet is marked as "cooked", then it means that the
|
||||
* packet was directed here by "any" interface and was already
|
||||
* cooked mode captured. So no need to clone it here.
|
||||
*/
|
||||
if (net_pkt_is_cooked_mode(pkt)) {
|
||||
skip_clone = true;
|
||||
}
|
||||
|
||||
if (skip_clone) {
|
||||
captured = pkt;
|
||||
} else {
|
||||
orig_slab = pkt->slab;
|
||||
pkt->slab = get_net_pkt();
|
||||
|
||||
|
@ -540,6 +552,7 @@ void net_capture_pkt(struct net_if *iface, struct net_pkt *pkt)
|
|||
/* TODO: update capture data statistics */
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
net_pkt_set_orig_iface(captured, iface);
|
||||
net_pkt_set_iface(captured, ctx->tunnel_iface);
|
||||
|
@ -550,6 +563,8 @@ void net_capture_pkt(struct net_if *iface, struct net_pkt *pkt)
|
|||
net_pkt_unref(captured);
|
||||
}
|
||||
|
||||
net_pkt_set_cooked_mode(pkt, false);
|
||||
|
||||
goto out;
|
||||
}
|
||||
|
||||
|
|
460
subsys/net/lib/capture/cooked.c
Normal file
460
subsys/net/lib/capture/cooked.c
Normal file
|
@ -0,0 +1,460 @@
|
|||
/*
|
||||
* Copyright (c) 2024 Nordic Semiconductor
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <zephyr/logging/log.h>
|
||||
LOG_MODULE_REGISTER(net_cooked, CONFIG_NET_CAPTURE_LOG_LEVEL);
|
||||
|
||||
#include <zephyr/kernel.h>
|
||||
#include <stdlib.h>
|
||||
#include <zephyr/net/net_core.h>
|
||||
#include <zephyr/net/net_ip.h>
|
||||
#include <zephyr/net/net_if.h>
|
||||
#include <zephyr/net/net_pkt.h>
|
||||
#include <zephyr/net/ethernet.h>
|
||||
#include <zephyr/net/virtual.h>
|
||||
#include <zephyr/net/virtual_mgmt.h>
|
||||
#include <zephyr/net/capture.h>
|
||||
#include <zephyr/net/net_l2.h>
|
||||
|
||||
#include "sll.h"
|
||||
|
||||
#define BUF_ALLOC_TIMEOUT 100 /* ms */
|
||||
|
||||
/* Use our own slabs for temporary pkts */
|
||||
NET_PKT_SLAB_DEFINE(cooked_pkts, CONFIG_NET_CAPTURE_PKT_COUNT);
|
||||
|
||||
#if defined(CONFIG_NET_BUF_FIXED_DATA_SIZE)
|
||||
NET_BUF_POOL_FIXED_DEFINE(cooked_bufs, CONFIG_NET_CAPTURE_BUF_COUNT,
|
||||
CONFIG_NET_BUF_DATA_SIZE, 4, NULL);
|
||||
#else
|
||||
NET_BUF_POOL_VAR_DEFINE(cooked_bufs, CONFIG_NET_CAPTURE_BUF_COUNT,
|
||||
CONFIG_NET_BUF_DATA_POOL_SIZE, 4, NULL);
|
||||
#endif
|
||||
|
||||
#define COOKED_MTU 1024
|
||||
#define COOKED_DEVICE "NET_COOKED"
|
||||
|
||||
struct cooked_context {
|
||||
struct net_if *iface;
|
||||
struct net_if *attached_to;
|
||||
|
||||
/* -1 is used as a not configured link type */
|
||||
int link_types[CONFIG_NET_CAPTURE_COOKED_MODE_MAX_LINK_TYPES];
|
||||
int link_type_count;
|
||||
int mtu;
|
||||
bool init_done;
|
||||
bool status;
|
||||
};
|
||||
|
||||
static void iface_init(struct net_if *iface)
|
||||
{
|
||||
struct cooked_context *ctx = net_if_get_device(iface)->data;
|
||||
struct net_if *any_iface;
|
||||
int ifindex;
|
||||
int ret;
|
||||
|
||||
ifindex = net_if_get_by_name("any");
|
||||
if (ifindex < 0) {
|
||||
NET_DBG("No such interface \"any\", cannot init interface %d",
|
||||
net_if_get_by_iface(iface));
|
||||
return;
|
||||
}
|
||||
|
||||
if (net_if_l2(net_if_get_by_index(ifindex)) != &NET_L2_GET_NAME(DUMMY)) {
|
||||
NET_DBG("The \"any\" interface %d is wrong type", ifindex);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ctx->init_done) {
|
||||
return;
|
||||
}
|
||||
|
||||
ctx->iface = iface;
|
||||
any_iface = net_if_get_by_index(ifindex);
|
||||
|
||||
(void)net_if_set_name(iface,
|
||||
CONFIG_NET_CAPTURE_COOKED_MODE_INTERFACE_NAME);
|
||||
(void)net_virtual_set_name(iface, "Cooked mode capture");
|
||||
|
||||
net_if_flag_set(iface, NET_IF_NO_AUTO_START);
|
||||
net_if_flag_set(iface, NET_IF_POINTOPOINT);
|
||||
net_if_flag_clear(iface, NET_IF_IPV4);
|
||||
net_if_flag_clear(iface, NET_IF_IPV6);
|
||||
|
||||
/* Hook into "any" interface so that we can receive the
|
||||
* captured data.
|
||||
*/
|
||||
ret = net_virtual_interface_attach(ctx->iface, any_iface);
|
||||
if (ret < 0) {
|
||||
NET_DBG("Cannot hook into interface %d (%d)",
|
||||
net_if_get_by_iface(any_iface), ret);
|
||||
return;
|
||||
}
|
||||
|
||||
NET_DBG("Interface %d attached on top of %d",
|
||||
net_if_get_by_iface(ctx->iface),
|
||||
net_if_get_by_iface(any_iface));
|
||||
|
||||
ctx->init_done = true;
|
||||
}
|
||||
|
||||
static int dev_init(const struct device *dev)
|
||||
{
|
||||
struct cooked_context *ctx = dev->data;
|
||||
|
||||
memset(ctx->link_types, -1, sizeof(ctx->link_types));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int interface_start(const struct device *dev)
|
||||
{
|
||||
struct cooked_context *ctx = dev->data;
|
||||
int ret = 0;
|
||||
|
||||
if (ctx->status) {
|
||||
return -EALREADY;
|
||||
}
|
||||
|
||||
ctx->status = true;
|
||||
|
||||
NET_DBG("Starting iface %d", net_if_get_by_iface(ctx->iface));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int interface_stop(const struct device *dev)
|
||||
{
|
||||
struct cooked_context *ctx = dev->data;
|
||||
|
||||
if (!ctx->status) {
|
||||
return -EALREADY;
|
||||
}
|
||||
|
||||
ctx->status = false;
|
||||
|
||||
NET_DBG("Stopping iface %d", net_if_get_by_iface(ctx->iface));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static enum net_verdict interface_recv(struct net_if *iface, struct net_pkt *pkt)
|
||||
{
|
||||
struct cooked_context *ctx = net_if_get_device(iface)->data;
|
||||
bool found = false;
|
||||
uint16_t ptype;
|
||||
|
||||
/* Feed the packet to capture system after verifying that we are capturing
|
||||
* these types of packets.
|
||||
* The packet will be freed by capture API after it has been processed.
|
||||
*/
|
||||
|
||||
ptype = net_pkt_ll_proto_type(pkt);
|
||||
|
||||
NET_DBG("Capture pkt %p for interface %d", pkt, net_if_get_by_iface(iface));
|
||||
|
||||
for (int i = 0; i < ctx->link_type_count; i++) {
|
||||
if (ctx->link_types[i] == ptype) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (found) {
|
||||
int ret;
|
||||
|
||||
NET_DBG("Handler found for packet type 0x%04x", ptype);
|
||||
|
||||
/* Normally capture API will clone the net_pkt and we would
|
||||
* always need to unref it. But for cooked packets, we can avoid
|
||||
* the cloning so need to unref only if there was an error with
|
||||
* capturing.
|
||||
*/
|
||||
ret = net_capture_pkt_with_status(iface, pkt);
|
||||
if (ret < 0) {
|
||||
net_pkt_unref(pkt);
|
||||
}
|
||||
|
||||
return NET_OK;
|
||||
}
|
||||
|
||||
NET_DBG("No handler found for packet type 0x%04x", ptype);
|
||||
|
||||
return NET_DROP;
|
||||
}
|
||||
|
||||
static int interface_attach(struct net_if *iface, struct net_if *lower_iface)
|
||||
{
|
||||
struct cooked_context *ctx;
|
||||
|
||||
if (net_if_get_by_iface(iface) < 0) {
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
ctx = net_if_get_device(iface)->data;
|
||||
ctx->attached_to = lower_iface;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int interface_set_config(struct net_if *iface,
|
||||
enum virtual_interface_config_type type,
|
||||
const struct virtual_interface_config *config)
|
||||
{
|
||||
struct cooked_context *ctx = net_if_get_device(iface)->data;
|
||||
|
||||
switch (type) {
|
||||
case VIRTUAL_INTERFACE_CONFIG_TYPE_LINK_TYPE:
|
||||
if (config->link_types.count > ARRAY_SIZE(ctx->link_types)) {
|
||||
return -ERANGE;
|
||||
}
|
||||
|
||||
for (int i = 0; i < config->link_types.count; i++) {
|
||||
NET_DBG("Adding link type %u", config->link_types.type[i]);
|
||||
|
||||
ctx->link_types[i] = (int)config->link_types.type[i];
|
||||
}
|
||||
|
||||
ctx->link_type_count = config->link_types.count;
|
||||
|
||||
/* Mark the rest of the types as invalid */
|
||||
for (int i = ctx->link_type_count; i < ARRAY_SIZE(ctx->link_types); i++) {
|
||||
ctx->link_types[i] = -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
case VIRTUAL_INTERFACE_CONFIG_TYPE_MTU:
|
||||
NET_DBG("Interface %d MTU set to %d",
|
||||
net_if_get_by_iface(iface), config->mtu);
|
||||
net_if_set_mtu(iface, config->mtu);
|
||||
return 0;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
static int interface_get_config(struct net_if *iface,
|
||||
enum virtual_interface_config_type type,
|
||||
struct virtual_interface_config *config)
|
||||
{
|
||||
struct cooked_context *ctx = net_if_get_device(iface)->data;
|
||||
int i;
|
||||
|
||||
switch (type) {
|
||||
case VIRTUAL_INTERFACE_CONFIG_TYPE_LINK_TYPE:
|
||||
for (i = 0; i < ctx->link_type_count; i++) {
|
||||
if (ctx->link_types[i] < 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
config->link_types.type[i] = (uint16_t)ctx->link_types[i];
|
||||
}
|
||||
|
||||
config->link_types.count = i;
|
||||
NET_ASSERT(config->link_types.count == ctx->link_type_count);
|
||||
return 0;
|
||||
|
||||
case VIRTUAL_INTERFACE_CONFIG_TYPE_MTU:
|
||||
config->mtu = net_if_get_mtu(iface);
|
||||
return 0;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
static const struct virtual_interface_api cooked_api = {
|
||||
.iface_api.init = iface_init,
|
||||
|
||||
.start = interface_start,
|
||||
.stop = interface_stop,
|
||||
.recv = interface_recv,
|
||||
.attach = interface_attach,
|
||||
.set_config = interface_set_config,
|
||||
.get_config = interface_get_config,
|
||||
};
|
||||
|
||||
static struct cooked_context cooked_context_data;
|
||||
|
||||
NET_VIRTUAL_INTERFACE_INIT(cooked, COOKED_DEVICE, dev_init,
|
||||
NULL, &cooked_context_data, NULL,
|
||||
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT,
|
||||
&cooked_api, COOKED_MTU);
|
||||
|
||||
int net_capture_cooked_setup(struct net_capture_cooked *ctx,
|
||||
uint16_t hatype,
|
||||
uint16_t halen,
|
||||
uint8_t *addr)
|
||||
{
|
||||
if (halen == 0 || halen > NET_CAPTURE_LL_ADDRLEN) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
memset(ctx, 0, sizeof(*ctx));
|
||||
|
||||
ctx->hatype = hatype;
|
||||
ctx->halen = halen;
|
||||
|
||||
memcpy(ctx->addr, addr, halen);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int create_sll_header(struct net_if *iface,
|
||||
struct net_pkt *pkt,
|
||||
struct net_capture_cooked *ctx,
|
||||
enum net_capture_packet_type type,
|
||||
uint16_t ptype)
|
||||
{
|
||||
struct sll2_header hdr2;
|
||||
struct sll_header hdr1;
|
||||
size_t hdr_len;
|
||||
uint8_t *hdr;
|
||||
int ret;
|
||||
|
||||
if (IS_ENABLED(CONFIG_NET_CAPTURE_COOKED_MODE_SLLV1)) {
|
||||
hdr1.sll_pkttype = htons(type);
|
||||
hdr1.sll_hatype = htons(ctx->hatype);
|
||||
hdr1.sll_halen = htons(ctx->halen);
|
||||
memcpy(hdr1.sll_addr, ctx->addr, sizeof(ctx->addr));
|
||||
hdr1.sll_protocol = htons(ptype);
|
||||
|
||||
hdr = (uint8_t *)&hdr1;
|
||||
hdr_len = sizeof(hdr1);
|
||||
|
||||
} else {
|
||||
hdr2.sll2_protocol = htons(ptype);
|
||||
hdr2.sll2_reserved_mbz = 0;
|
||||
hdr2.sll2_if_index = net_if_get_by_iface(iface);
|
||||
hdr2.sll2_hatype = htons(ctx->hatype);
|
||||
hdr2.sll2_pkttype = htons(type);
|
||||
hdr2.sll2_halen = htons(ctx->halen);
|
||||
memcpy(hdr2.sll2_addr, ctx->addr, sizeof(ctx->addr));
|
||||
|
||||
hdr = (uint8_t *)&hdr2;
|
||||
hdr_len = sizeof(hdr2);
|
||||
}
|
||||
|
||||
ret = net_pkt_write(pkt, hdr, hdr_len);
|
||||
if (ret < 0) {
|
||||
NET_DBG("Cannot write sll%s header (%d)",
|
||||
IS_ENABLED(CONFIG_NET_CAPTURE_COOKED_MODE_SLLV1) ?
|
||||
"" : "2", ret);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct k_mem_slab *get_net_pkt(void)
|
||||
{
|
||||
return &cooked_pkts;
|
||||
}
|
||||
|
||||
static struct net_buf_pool *get_net_buf(void)
|
||||
{
|
||||
return &cooked_bufs;
|
||||
}
|
||||
|
||||
void net_capture_data(struct net_capture_cooked *ctx,
|
||||
const uint8_t *data, size_t data_len,
|
||||
enum net_capture_packet_type type,
|
||||
uint16_t ptype)
|
||||
{
|
||||
static struct net_context context;
|
||||
const struct device *dev;
|
||||
struct net_if *iface;
|
||||
struct net_pkt *pkt;
|
||||
int ret;
|
||||
|
||||
net_context_setup_pools(&context, get_net_pkt, get_net_buf);
|
||||
|
||||
pkt = net_pkt_alloc_from_slab(&cooked_pkts, K_MSEC(BUF_ALLOC_TIMEOUT));
|
||||
if (pkt == NULL) {
|
||||
NET_DBG("Cannot allocate %s", "net_pkt");
|
||||
return;
|
||||
}
|
||||
|
||||
net_pkt_set_context(pkt, &context);
|
||||
|
||||
ret = net_pkt_alloc_buffer_raw(pkt,
|
||||
sizeof(COND_CODE_1(CONFIG_NET_CAPTURE_COOKED_MODE_SLLV1,
|
||||
(struct sll_header),
|
||||
(struct sll2_header))) + data_len,
|
||||
K_MSEC(BUF_ALLOC_TIMEOUT));
|
||||
if (ret < 0) {
|
||||
NET_DBG("Cannot allocate %s %zd bytes (%d)", "net_buf for",
|
||||
sizeof(struct sll_header) + data_len, ret);
|
||||
net_pkt_unref(pkt);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Write the packet to "any" interface which will pass it to
|
||||
* the virtual interface that does the actual capturing of the packet.
|
||||
* The reason for this trickery is that we do not have a network
|
||||
* interface in use in this API, and want to have the packet routed
|
||||
* via the any interface which can then deliver it to registered
|
||||
* virtual interfaces.
|
||||
*/
|
||||
dev = device_get_binding(COOKED_DEVICE);
|
||||
if (dev == NULL) {
|
||||
NET_DBG("No such device %s found, data not captured!",
|
||||
COOKED_DEVICE);
|
||||
net_pkt_unref(pkt);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Next we feed the data to the any interface, which
|
||||
* will pass the data to the virtual interface.
|
||||
* When using capture API or net-shell capture command, one
|
||||
* should capture packets from the virtual interface.
|
||||
*/
|
||||
iface = ((struct cooked_context *)dev->data)->attached_to;
|
||||
|
||||
ret = create_sll_header(iface, pkt, ctx, type, ptype);
|
||||
if (ret < 0) {
|
||||
NET_DBG("Cannot write %s %zd bytes (%d)", "header",
|
||||
sizeof(struct sll_header), ret);
|
||||
net_pkt_unref(pkt);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = net_pkt_write(pkt, data, data_len);
|
||||
if (ret < 0) {
|
||||
NET_DBG("Cannot write %s %zd bytes (%d)", "payload",
|
||||
data_len, ret);
|
||||
net_pkt_unref(pkt);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Mark that this packet came from cooked capture mode.
|
||||
* This will prevent the capture API from cloning the packet,
|
||||
* so that the net_pkt will be passed as is to capture interface.
|
||||
*/
|
||||
net_pkt_set_cooked_mode(pkt, true);
|
||||
|
||||
/* The protocol type is used by virtual cooked interface to decide
|
||||
* whether we capture the packet or not.
|
||||
*/
|
||||
net_pkt_set_ll_proto_type(pkt, ptype);
|
||||
|
||||
net_pkt_lladdr_src(pkt)->addr = NULL;
|
||||
net_pkt_lladdr_src(pkt)->len = 0U;
|
||||
net_pkt_lladdr_src(pkt)->type = NET_LINK_DUMMY;
|
||||
net_pkt_lladdr_dst(pkt)->addr = NULL;
|
||||
net_pkt_lladdr_dst(pkt)->len = 0U;
|
||||
net_pkt_lladdr_dst(pkt)->type = NET_LINK_DUMMY;
|
||||
|
||||
ret = net_recv_data(iface, pkt);
|
||||
if (ret < 0) {
|
||||
net_pkt_unref(pkt);
|
||||
}
|
||||
}
|
47
subsys/net/lib/capture/sll.h
Normal file
47
subsys/net/lib/capture/sll.h
Normal file
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright (c) 2024 Nordic Semiconductor
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/* Defines for Linux cooked mode capture (SLL) */
|
||||
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/types.h>
|
||||
|
||||
/* Useful information about SLL header format can be found here
|
||||
* https://www.tcpdump.org/linktypes/LINKTYPE_LINUX_SLL.html
|
||||
*/
|
||||
|
||||
#define SLL_HDR_LEN 16 /* Total header length */
|
||||
#define SLL_ADDRLEN 8 /* Length of the address field */
|
||||
|
||||
struct sll_header {
|
||||
uint16_t sll_pkttype; /* Packet type */
|
||||
uint16_t sll_hatype; /* Link-layer address type */
|
||||
uint16_t sll_halen; /* Link-layer address length */
|
||||
uint8_t sll_addr[SLL_ADDRLEN]; /* Link-layer address */
|
||||
uint16_t sll_protocol; /* Protocol */
|
||||
};
|
||||
|
||||
BUILD_ASSERT(sizeof(struct sll_header) == SLL_HDR_LEN);
|
||||
|
||||
#define SLL2_HDR_LEN 20 /* Total header length */
|
||||
|
||||
struct sll2_header {
|
||||
uint16_t sll2_protocol; /* Protocol */
|
||||
uint16_t sll2_reserved_mbz; /* Reserved - must be zero */
|
||||
uint32_t sll2_if_index; /* 1-based interface index */
|
||||
uint16_t sll2_hatype; /* Link-layer address type */
|
||||
uint8_t sll2_pkttype; /* Packet type */
|
||||
uint8_t sll2_halen; /* Link-layer address length */
|
||||
uint8_t sll2_addr[SLL_ADDRLEN]; /* Link-layer address */
|
||||
};
|
||||
|
||||
BUILD_ASSERT(sizeof(struct sll2_header) == SLL2_HDR_LEN);
|
||||
|
||||
#define SLL_HOST 0 /* packet was sent to us by somebody else */
|
||||
#define SLL_BROADCAST 1 /* packet was broadcast by somebody else */
|
||||
#define SLL_MULTICAST 2 /* packet was multicast, but not broadcast, by somebody else */
|
||||
#define SLL_OTHERHOST 3 /* packet was sent by somebody else to somebody else */
|
||||
#define SLL_OUTGOING 4 /* packet was sent by us */
|
Loading…
Reference in a new issue