net: icmpv4: Add support for Timestamp and RR

Timestamp and RecordRoute options are supported
in only ICMPv4 EchoRequest call as per
RFC 1122 3.2.2.6.

Fixes #14668

Signed-off-by: Ravi kumar Veeramally <ravikumar.veeramally@linux.intel.com>
This commit is contained in:
Ravi kumar Veeramally 2019-09-30 12:22:01 +03:00 committed by Jukka Rissanen
parent cf9ad748ba
commit 1c33951318
4 changed files with 527 additions and 11 deletions

View file

@ -64,6 +64,13 @@ config NET_IPV4_AUTO
help
Enables IPv4 auto IP address configuration (see RFC 3927)
config NET_IPV4_HDR_OPTIONS
bool "Enable IPv4 Header options support"
help
Enables IPv4 header options support. Current support for only
ICMPv4 Echo request. Only RecordRoute and Timestamp are handled.
module = NET_IPV4
module-dep = NET_LOG
module-str = Log level for core IPv4

View file

@ -25,6 +25,11 @@ LOG_MODULE_REGISTER(net_icmpv4, CONFIG_NET_ICMPV4_LOG_LEVEL);
static sys_slist_t handlers;
struct net_icmpv4_hdr_opts_data {
struct net_pkt *reply;
const struct in_addr *src;
};
static int icmpv4_create(struct net_pkt *pkt, u8_t icmp_type, u8_t icmp_code)
{
NET_PKT_DATA_ACCESS_CONTIGUOUS_DEFINE(icmpv4_access,
@ -49,6 +54,12 @@ int net_icmpv4_finalize(struct net_pkt *pkt)
struct net_icmp_hdr);
struct net_icmp_hdr *icmp_hdr;
if (IS_ENABLED(CONFIG_NET_IPV4_HDR_OPTIONS)) {
if (net_pkt_skip(pkt, net_pkt_ipv4_opts_len(pkt))) {
return -ENOBUFS;
}
}
icmp_hdr = (struct net_icmp_hdr *)net_pkt_get_data(pkt, &icmpv4_access);
if (!icmp_hdr) {
return -ENOBUFS;
@ -59,9 +70,346 @@ int net_icmpv4_finalize(struct net_pkt *pkt)
return net_pkt_set_data(pkt, &icmpv4_access);
}
#if defined(CONFIG_NET_IPV4_HDR_OPTIONS)
/* Parse Record Route and add our own IP address based on
* free entries.
*/
static int icmpv4_update_record_route(u8_t *opt_data,
u8_t opt_len,
struct net_pkt *reply,
const struct in_addr *src)
{
u8_t len = net_pkt_ipv4_opts_len(reply);
u8_t addr_len = sizeof(struct in_addr);
u8_t ptr_offset = 4U;
u8_t offset = 0U;
u8_t skip;
u8_t ptr;
if (net_pkt_write_u8(reply, NET_IPV4_OPTS_RR)) {
goto drop;
}
len++;
if (net_pkt_write_u8(reply, opt_len + 2U)) {
goto drop;
}
len++;
/* The third octet is the pointer into the route data
* indicating the octet which begins the next area to
* store a route address. The pointer is relative to
* this option, and the smallest legal value for the
* pointer is 4.
*/
ptr = opt_data[offset++];
/* If the route data area is already full (the pointer exceeds
* the length) the datagram is forwarded without inserting the
* address into the recorded route.
*/
if (ptr >= opt_len) {
/* No free entry to update RecordRoute */
if (net_pkt_write_u8(reply, ptr)) {
goto drop;
}
len++;
if (net_pkt_write(reply, opt_data + offset, opt_len)) {
goto drop;
}
len += opt_len;
net_pkt_set_ipv4_opts_len(reply, len);
return 0;
}
/* If there is some room but not enough room for a full address
* to be inserted, the original datagram is considered to be in
* error and is discarded.
*/
if ((ptr + addr_len) > opt_len) {
goto drop;
}
/* So, there is a free entry to update Record Route */
if (net_pkt_write_u8(reply, ptr + addr_len)) {
goto drop;
}
len++;
skip = ptr - ptr_offset;
if (skip) {
/* Do not alter existed routes */
if (net_pkt_write(reply, opt_data + offset, skip)) {
goto drop;
}
offset += skip;
len += skip;
}
if (net_pkt_write(reply, (void *)src, addr_len)) {
goto drop;
}
len += addr_len;
offset += addr_len;
if (opt_len > offset) {
if (net_pkt_write(reply, opt_data + offset, opt_len - offset)) {
goto drop;
}
}
len += opt_len - offset;
net_pkt_set_ipv4_opts_len(reply, len);
return 0;
drop:
return -EINVAL;
}
/* TODO: Timestamp value should updated, as per RFC 791
* Internet Timestamp. Timestamp value : 32-bit timestamp
* in milliseconds since midnight UT.
*/
static int icmpv4_update_time_stamp(u8_t *opt_data,
u8_t opt_len,
struct net_pkt *reply,
const struct in_addr *src)
{
u8_t len = net_pkt_ipv4_opts_len(reply);
u8_t addr_len = sizeof(struct in_addr);
u8_t ptr_offset = 5U;
u8_t offset = 0U;
u8_t new_entry_len;
u8_t overflow;
u8_t flag;
u8_t skip;
u8_t ptr;
if (net_pkt_write_u8(reply, NET_IPV4_OPTS_TS)) {
goto drop;
}
len++;
if (net_pkt_write_u8(reply, opt_len + 2U)) {
goto drop;
}
len++;
/* The Pointer is the number of octets from the beginning of
* this option to the end of timestamps plus one (i.e., it
* points to the octet beginning the space for next timestamp).
* The smallest legal value is 5. The timestamp area is full
* when the pointer is greater than the length.
*/
ptr = opt_data[offset++];
flag = opt_data[offset++];
flag = flag & 0x0F;
overflow = (flag & 0xF0) >> 4U;
/* If the timestamp data area is already full (the pointer
* exceeds the length) the datagram is forwarded without
* inserting the timestamp, but the overflow count is
* incremented by one.
*/
if (ptr >= opt_len) {
/* overflow count itself overflows, the original datagram
* is considered to be in error and is discarded.
*/
if (overflow == 0x0F) {
goto drop;
}
/* No free entry to update Timestamp data */
if (net_pkt_write_u8(reply, ptr)) {
goto drop;
}
len++;
overflow++;
flag = (overflow << 4U) | flag;
if (net_pkt_write_u8(reply, flag)) {
goto drop;
}
len++;
if (net_pkt_write(reply, opt_data + offset, opt_len)) {
goto drop;
}
len += opt_len;
net_pkt_set_ipv4_opts_len(reply, len);
return 0;
}
switch (flag) {
case NET_IPV4_TS_OPT_TS_ONLY:
new_entry_len = sizeof(u32_t);
break;
case NET_IPV4_TS_OPT_TS_ADDR:
new_entry_len = addr_len + sizeof(u32_t);
break;
case NET_IPV4_TS_OPT_TS_PRES: /* TODO */
default:
goto drop;
}
/* So, there is a free entry to update Timestamp */
if (net_pkt_write_u8(reply, ptr + new_entry_len)) {
goto drop;
}
len++;
if (net_pkt_write_u8(reply, (overflow << 4) | flag)) {
goto drop;
}
len++;
skip = ptr - ptr_offset;
if (skip) {
/* Do not alter existed routes */
if (net_pkt_write(reply, opt_data + offset, skip)) {
goto drop;
}
len += skip;
offset += skip;
}
switch (flag) {
case NET_IPV4_TS_OPT_TS_ONLY:
if (net_pkt_write_be32(reply, htons(k_uptime_get_32()))) {
goto drop;
}
len += sizeof(u32_t);
offset += sizeof(u32_t);
break;
case NET_IPV4_TS_OPT_TS_ADDR:
if (net_pkt_write(reply, (void *)src, addr_len)) {
goto drop;
}
len += addr_len;
if (net_pkt_write_be32(reply, htons(k_uptime_get_32()))) {
goto drop;
}
len += sizeof(u32_t);
offset += (addr_len + sizeof(u32_t));
break;
}
if (opt_len > offset) {
if (net_pkt_write(reply, opt_data + offset, opt_len - offset)) {
goto drop;
}
}
len += opt_len - offset;
net_pkt_set_ipv4_opts_len(reply, len);
return 0;
drop:
return -EINVAL;
}
static int icmpv4_reply_to_options(u8_t opt_type,
u8_t *opt_data,
u8_t opt_len,
void *user_data)
{
struct net_icmpv4_hdr_opts_data *ud =
(struct net_icmpv4_hdr_opts_data *)user_data;
if (opt_type == NET_IPV4_OPTS_RR) {
return icmpv4_update_record_route(opt_data, opt_len,
ud->reply, ud->src);
} else if (opt_type == NET_IPV4_OPTS_TS) {
return icmpv4_update_time_stamp(opt_data, opt_len,
ud->reply, ud->src);
}
return 0;
}
static int icmpv4_handle_header_options(struct net_pkt *pkt,
struct net_pkt *reply,
const struct in_addr *src)
{
struct net_icmpv4_hdr_opts_data ud;
u8_t len;
ud.reply = reply;
ud.src = src;
if (net_ipv4_parse_hdr_options(pkt, icmpv4_reply_to_options, &ud)) {
return -EINVAL;
}
len = net_pkt_ipv4_opts_len(reply);
/* IPv4 optional header part should ends in 32 bit boundary */
if (len % 4U != 0U) {
u8_t i = 4U - (len % 4U);
if (net_pkt_memset(reply, NET_IPV4_OPTS_NOP, i)) {
return -EINVAL;
}
len += i;
}
/* Options are added now, update the header length. */
net_pkt_set_ipv4_opts_len(reply, len);
return 0;
}
#else
static int icmpv4_handle_header_options(struct net_pkt *pkt,
struct net_pkt *reply,
const struct in_addr *src)
{
ARG_UNUSED(pkt);
ARG_UNUSED(reply);
ARG_UNUSED(src);
return 0;
}
#endif
static enum net_verdict icmpv4_handle_echo_request(struct net_pkt *pkt,
struct net_ipv4_hdr *ip_hdr,
struct net_icmp_hdr *icmp_hdr)
struct net_ipv4_hdr *ip_hdr,
struct net_icmp_hdr *icmp_hdr)
{
struct net_pkt *reply = NULL;
const struct in_addr *src;
@ -80,13 +428,16 @@ static enum net_verdict icmpv4_handle_echo_request(struct net_pkt *pkt,
log_strdup(net_sprint_ipv4_addr(&ip_hdr->dst)));
payload_len = net_pkt_get_len(pkt) -
net_pkt_ip_hdr_len(pkt) - NET_ICMPH_LEN;
net_pkt_ip_hdr_len(pkt) -
net_pkt_ipv4_opts_len(pkt) - NET_ICMPH_LEN;
if (payload_len < NET_ICMPV4_UNUSED_LEN) {
/* No identifier or sequence number present */
goto drop;
}
reply = net_pkt_alloc_with_buffer(net_pkt_iface(pkt), payload_len,
reply = net_pkt_alloc_with_buffer(net_pkt_iface(pkt),
net_pkt_ipv4_opts_len(pkt) +
payload_len,
AF_INET, IPPROTO_ICMP,
PKT_WAIT_TIME);
if (!reply) {
@ -101,10 +452,19 @@ static enum net_verdict icmpv4_handle_echo_request(struct net_pkt *pkt,
src = &ip_hdr->dst;
}
if (net_ipv4_create(reply, src, &ip_hdr->src) ||
icmpv4_create(reply, NET_ICMPV4_ECHO_REPLY, 0) ||
if (net_ipv4_create(reply, src, &ip_hdr->src)) {
goto drop;
}
if (IS_ENABLED(CONFIG_NET_IPV4_HDR_OPTIONS)) {
if (net_pkt_ipv4_opts_len(pkt) &&
icmpv4_handle_header_options(pkt, reply, src)) {
goto drop;
}
}
if (icmpv4_create(reply, NET_ICMPV4_ECHO_REPLY, 0) ||
net_pkt_copy(reply, pkt, payload_len)) {
NET_DBG("DROP: wrong buffer");
goto drop;
}

View file

@ -79,10 +79,12 @@ int net_ipv4_finalize(struct net_pkt *pkt, u8_t next_header_proto)
return -ENOBUFS;
}
if (net_pkt_ipv4_opts_len(pkt)) {
ipv4_hdr->vhl = 0x40 | (0x0F &
((net_pkt_ip_hdr_len(pkt) +
net_pkt_ipv4_opts_len(pkt)) / 4U));
if (IS_ENABLED(CONFIG_NET_IPV4_HDR_OPTIONS)) {
if (net_pkt_ipv4_opts_len(pkt)) {
ipv4_hdr->vhl = 0x40 | (0x0F &
((net_pkt_ip_hdr_len(pkt) +
net_pkt_ipv4_opts_len(pkt)) / 4U));
}
}
ipv4_hdr->len = htons(net_pkt_get_len(pkt));
@ -107,6 +109,93 @@ int net_ipv4_finalize(struct net_pkt *pkt, u8_t next_header_proto)
return 0;
}
#if defined(CONFIG_NET_IPV4_HDR_OPTIONS)
int net_ipv4_parse_hdr_options(struct net_pkt *pkt,
net_ipv4_parse_hdr_options_cb_t cb,
void *user_data)
{
struct net_pkt_cursor cur;
u8_t opt_data[NET_IPV4_HDR_OPTNS_MAX_LEN];
u8_t opts_len;
if (!cb) {
return -EINVAL;
}
net_pkt_cursor_backup(pkt, &cur);
net_pkt_cursor_init(pkt);
if (net_pkt_skip(pkt, sizeof(struct net_ipv4_hdr))) {
return -EINVAL;
}
opts_len = net_pkt_ipv4_opts_len(pkt);
while (opts_len) {
u8_t opt_len = 0U;
u8_t opt_type;
if (net_pkt_read_u8(pkt, &opt_type)) {
return -EINVAL;
}
opts_len--;
if (!(opt_type == NET_IPV4_OPTS_EO ||
opt_type == NET_IPV4_OPTS_NOP)) {
if (net_pkt_read_u8(pkt, &opt_len)) {
return -EINVAL;
}
opt_len -= 2U;
opts_len--;
}
if (opt_len > opts_len) {
return -EINVAL;
}
switch (opt_type) {
case NET_IPV4_OPTS_NOP:
break;
case NET_IPV4_OPTS_EO:
/* Options length should be zero, when cursor reachs to
* End of options.
*/
if (opts_len) {
return -EINVAL;
}
break;
case NET_IPV4_OPTS_RR:
case NET_IPV4_OPTS_TS:
if (net_pkt_read(pkt, opt_data, opt_len)) {
return -EINVAL;
}
if (cb(opt_type, opt_data, opt_len, user_data)) {
return -EINVAL;
}
break;
default:
if (net_pkt_skip(pkt, opt_len)) {
return -EINVAL;
}
break;
}
opts_len -= opt_len;
}
net_pkt_cursor_restore(pkt, &cur);
return 0;
}
#endif
enum net_verdict net_ipv4_input(struct net_pkt *pkt)
{
NET_PKT_DATA_ACCESS_CONTIGUOUS_DEFINE(ipv4_access, struct net_ipv4_hdr);

View file

@ -22,6 +22,19 @@
#define NET_IPV4_IHL_MASK 0x0F
/* IPv4 Options */
#define NET_IPV4_OPTS_EO 0 /* End of Options */
#define NET_IPV4_OPTS_NOP 1 /* No operation */
#define NET_IPV4_OPTS_RR 7 /* Record Route */
#define NET_IPV4_OPTS_TS 68 /* Timestamp */
/* IPv4 Options Timestamp flags */
#define NET_IPV4_TS_OPT_TS_ONLY 0 /* Timestamp only */
#define NET_IPV4_TS_OPT_TS_ADDR 1 /* Timestamp and address */
#define NET_IPV4_TS_OPT_TS_PRES 3 /* Timestamp prespecified hops*/
#define NET_IPV4_HDR_OPTNS_MAX_LEN 40
/**
* @brief Create IPv4 packet in provided net_pkt.
*
@ -72,4 +85,51 @@ static inline int net_ipv4_finalize(struct net_pkt *pkt,
}
#endif
/**
* @typedef net_ipv4_parse_hdr_options_cb_t
* @brief IPv4 header options handle callback
*
* @details The callback is called when parser encounter
* supported options.
*
* @param opt_type Option type
* @param opt_data Option data
* @param opt_len Option length
* @param user_data Userdata given in net_ipv4_parse_hdr_options()
*
* @return 0 on success, negative otherwise.
*/
typedef int (*net_ipv4_parse_hdr_options_cb_t)(u8_t opt_type,
u8_t *opt_data,
u8_t opt_len,
void *user_data);
/**
* @brief Parse IPv4 header options.
* Parse the IPv4 header options and call the callback with
* options type, data and length along with user_data.
*
* @param pkt Network packet
* @param cb callback to handle IPv4 header options
* @param user_data User data
*
* @return 0 on success, negative otherwise.
*/
#if defined(CONFIG_NET_IPV4_HDR_OPTIONS)
int net_ipv4_parse_hdr_options(struct net_pkt *pkt,
net_ipv4_parse_hdr_options_cb_t cb,
void *user_data);
#else
static inline int net_ipv4_parse_hdr_options(struct net_pkt *pkt,
net_ipv4_parse_hdr_options_cb_t cb,
void *user_data)
{
ARG_UNUSED(pkt);
ARG_UNUSED(cb);
ARG_UNUSED(user_data);
return -ENOTSUP;
}
#endif
#endif /* __IPV4_H */