446 lines
10 KiB
C
446 lines
10 KiB
C
/** @file
|
|
* @brief IPv4 related functions
|
|
*/
|
|
|
|
/*
|
|
* Copyright (c) 2016 Intel Corporation
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <zephyr/logging/log.h>
|
|
LOG_MODULE_REGISTER(net_ipv4, CONFIG_NET_IPV4_LOG_LEVEL);
|
|
|
|
#include <errno.h>
|
|
#include <zephyr/net/net_core.h>
|
|
#include <zephyr/net/net_pkt.h>
|
|
#include <zephyr/net/net_stats.h>
|
|
#include <zephyr/net/net_context.h>
|
|
#include <zephyr/net/virtual.h>
|
|
#include "net_private.h"
|
|
#include "connection.h"
|
|
#include "net_stats.h"
|
|
#include "icmpv4.h"
|
|
#include "udp_internal.h"
|
|
#include "tcp_internal.h"
|
|
#include "dhcpv4/dhcpv4_internal.h"
|
|
#include "ipv4.h"
|
|
|
|
BUILD_ASSERT(sizeof(struct in_addr) == NET_IPV4_ADDR_SIZE);
|
|
|
|
/* Timeout for various buffer allocations in this file. */
|
|
#define NET_BUF_TIMEOUT K_MSEC(50)
|
|
|
|
int net_ipv4_create_full(struct net_pkt *pkt,
|
|
const struct in_addr *src,
|
|
const struct in_addr *dst,
|
|
uint8_t tos,
|
|
uint16_t id,
|
|
uint8_t flags,
|
|
uint16_t offset)
|
|
{
|
|
NET_PKT_DATA_ACCESS_CONTIGUOUS_DEFINE(ipv4_access, struct net_ipv4_hdr);
|
|
struct net_ipv4_hdr *ipv4_hdr;
|
|
|
|
ipv4_hdr = (struct net_ipv4_hdr *)net_pkt_get_data(pkt, &ipv4_access);
|
|
if (!ipv4_hdr) {
|
|
return -ENOBUFS;
|
|
}
|
|
|
|
ipv4_hdr->vhl = 0x45;
|
|
ipv4_hdr->tos = tos;
|
|
ipv4_hdr->len = 0U;
|
|
ipv4_hdr->id[0] = id >> 8;
|
|
ipv4_hdr->id[1] = id;
|
|
ipv4_hdr->offset[0] = (offset >> 8) | (flags << 5);
|
|
ipv4_hdr->offset[1] = offset;
|
|
|
|
ipv4_hdr->ttl = net_pkt_ipv4_ttl(pkt);
|
|
if (ipv4_hdr->ttl == 0U) {
|
|
if (net_ipv4_is_addr_mcast(dst)) {
|
|
if (net_pkt_context(pkt) != NULL) {
|
|
ipv4_hdr->ttl =
|
|
net_context_get_ipv4_mcast_ttl(net_pkt_context(pkt));
|
|
} else {
|
|
ipv4_hdr->ttl = net_if_ipv4_get_mcast_ttl(net_pkt_iface(pkt));
|
|
}
|
|
} else {
|
|
if (net_pkt_context(pkt) != NULL) {
|
|
ipv4_hdr->ttl =
|
|
net_context_get_ipv4_ttl(net_pkt_context(pkt));
|
|
} else {
|
|
ipv4_hdr->ttl = net_if_ipv4_get_ttl(net_pkt_iface(pkt));
|
|
}
|
|
}
|
|
}
|
|
|
|
ipv4_hdr->proto = 0U;
|
|
ipv4_hdr->chksum = 0U;
|
|
|
|
net_ipv4_addr_copy_raw(ipv4_hdr->dst, (uint8_t *)dst);
|
|
net_ipv4_addr_copy_raw(ipv4_hdr->src, (uint8_t *)src);
|
|
|
|
net_pkt_set_ip_hdr_len(pkt, sizeof(struct net_ipv4_hdr));
|
|
|
|
return net_pkt_set_data(pkt, &ipv4_access);
|
|
}
|
|
|
|
int net_ipv4_create(struct net_pkt *pkt,
|
|
const struct in_addr *src,
|
|
const struct in_addr *dst)
|
|
{
|
|
uint8_t tos = 0;
|
|
|
|
if (IS_ENABLED(CONFIG_NET_IP_DSCP_ECN)) {
|
|
net_ipv4_set_dscp(&tos, net_pkt_ip_dscp(pkt));
|
|
net_ipv4_set_ecn(&tos, net_pkt_ip_ecn(pkt));
|
|
}
|
|
|
|
return net_ipv4_create_full(pkt, src, dst, tos, 0U, 0U, 0U);
|
|
}
|
|
|
|
int net_ipv4_finalize(struct net_pkt *pkt, uint8_t next_header_proto)
|
|
{
|
|
NET_PKT_DATA_ACCESS_CONTIGUOUS_DEFINE(ipv4_access, struct net_ipv4_hdr);
|
|
struct net_ipv4_hdr *ipv4_hdr;
|
|
|
|
net_pkt_set_overwrite(pkt, true);
|
|
|
|
ipv4_hdr = (struct net_ipv4_hdr *)net_pkt_get_data(pkt, &ipv4_access);
|
|
if (!ipv4_hdr) {
|
|
return -ENOBUFS;
|
|
}
|
|
|
|
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));
|
|
ipv4_hdr->proto = next_header_proto;
|
|
|
|
if (net_if_need_calc_tx_checksum(net_pkt_iface(pkt))) {
|
|
ipv4_hdr->chksum = net_calc_chksum_ipv4(pkt);
|
|
}
|
|
|
|
net_pkt_set_data(pkt, &ipv4_access);
|
|
|
|
if (IS_ENABLED(CONFIG_NET_UDP) &&
|
|
next_header_proto == IPPROTO_UDP) {
|
|
return net_udp_finalize(pkt, false);
|
|
} else if (IS_ENABLED(CONFIG_NET_TCP) &&
|
|
next_header_proto == IPPROTO_TCP) {
|
|
return net_tcp_finalize(pkt, false);
|
|
} else if (next_header_proto == IPPROTO_ICMP) {
|
|
return net_icmpv4_finalize(pkt, false);
|
|
}
|
|
|
|
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;
|
|
uint8_t opt_data[NET_IPV4_HDR_OPTNS_MAX_LEN];
|
|
uint8_t total_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;
|
|
}
|
|
|
|
total_opts_len = net_pkt_ipv4_opts_len(pkt);
|
|
|
|
while (total_opts_len) {
|
|
uint8_t opt_len = 0U;
|
|
uint8_t opt_type;
|
|
|
|
if (net_pkt_read_u8(pkt, &opt_type)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
total_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;
|
|
}
|
|
|
|
if (opt_len < 2U || total_opts_len < 1U) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
opt_len -= 2U;
|
|
total_opts_len--;
|
|
}
|
|
|
|
if (opt_len > total_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 (total_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;
|
|
}
|
|
|
|
total_opts_len -= opt_len;
|
|
}
|
|
|
|
net_pkt_cursor_restore(pkt, &cur);
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
enum net_verdict net_ipv4_input(struct net_pkt *pkt, bool is_loopback)
|
|
{
|
|
NET_PKT_DATA_ACCESS_CONTIGUOUS_DEFINE(ipv4_access, struct net_ipv4_hdr);
|
|
NET_PKT_DATA_ACCESS_DEFINE(udp_access, struct net_udp_hdr);
|
|
NET_PKT_DATA_ACCESS_DEFINE(tcp_access, struct net_tcp_hdr);
|
|
int real_len = net_pkt_get_len(pkt);
|
|
enum net_verdict verdict = NET_DROP;
|
|
union net_proto_header proto_hdr;
|
|
struct net_ipv4_hdr *hdr;
|
|
union net_ip_header ip;
|
|
uint8_t hdr_len;
|
|
uint8_t opts_len;
|
|
int pkt_len;
|
|
|
|
#if defined(CONFIG_NET_L2_VIRTUAL)
|
|
struct net_pkt_cursor hdr_start;
|
|
|
|
net_pkt_cursor_backup(pkt, &hdr_start);
|
|
#endif
|
|
|
|
net_stats_update_ipv4_recv(net_pkt_iface(pkt));
|
|
|
|
hdr = (struct net_ipv4_hdr *)net_pkt_get_data(pkt, &ipv4_access);
|
|
if (!hdr) {
|
|
NET_DBG("DROP: no buffer");
|
|
goto drop;
|
|
}
|
|
|
|
hdr_len = (hdr->vhl & NET_IPV4_IHL_MASK) * 4U;
|
|
if (hdr_len < sizeof(struct net_ipv4_hdr)) {
|
|
NET_DBG("DROP: Invalid hdr length");
|
|
goto drop;
|
|
}
|
|
|
|
net_pkt_set_ip_hdr_len(pkt, sizeof(struct net_ipv4_hdr));
|
|
|
|
if (IS_ENABLED(CONFIG_NET_IP_DSCP_ECN)) {
|
|
net_pkt_set_ip_dscp(pkt, net_ipv4_get_dscp(hdr->tos));
|
|
net_pkt_set_ip_ecn(pkt, net_ipv4_get_ecn(hdr->tos));
|
|
}
|
|
|
|
opts_len = hdr_len - sizeof(struct net_ipv4_hdr);
|
|
if (opts_len > NET_IPV4_HDR_OPTNS_MAX_LEN) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (hdr->ttl == 0) {
|
|
goto drop;
|
|
}
|
|
|
|
net_pkt_set_ipv4_opts_len(pkt, opts_len);
|
|
|
|
pkt_len = ntohs(hdr->len);
|
|
if (real_len < pkt_len) {
|
|
NET_DBG("DROP: pkt len per hdr %d != pkt real len %d",
|
|
pkt_len, real_len);
|
|
goto drop;
|
|
} else if (real_len > pkt_len) {
|
|
net_pkt_update_length(pkt, pkt_len);
|
|
}
|
|
|
|
if (!is_loopback) {
|
|
if (net_ipv4_is_addr_loopback((struct in_addr *)hdr->dst) ||
|
|
net_ipv4_is_addr_loopback((struct in_addr *)hdr->src)) {
|
|
NET_DBG("DROP: localhost packet");
|
|
goto drop;
|
|
}
|
|
|
|
if (net_ipv4_is_my_addr((struct in_addr *)hdr->src)) {
|
|
NET_DBG("DROP: src addr is %s", "mine");
|
|
goto drop;
|
|
}
|
|
}
|
|
|
|
if (net_ipv4_is_addr_mcast((struct in_addr *)hdr->src)) {
|
|
NET_DBG("DROP: src addr is %s", "mcast");
|
|
goto drop;
|
|
}
|
|
|
|
if (net_ipv4_is_addr_bcast(net_pkt_iface(pkt), (struct in_addr *)hdr->src)) {
|
|
NET_DBG("DROP: src addr is %s", "bcast");
|
|
goto drop;
|
|
}
|
|
|
|
if (net_ipv4_is_addr_unspecified((struct in_addr *)hdr->src) &&
|
|
!net_ipv4_is_addr_bcast(net_pkt_iface(pkt), (struct in_addr *)hdr->dst)) {
|
|
NET_DBG("DROP: src addr is %s", "unspecified");
|
|
goto drop;
|
|
}
|
|
|
|
if (net_if_need_calc_rx_checksum(net_pkt_iface(pkt)) &&
|
|
net_calc_chksum_ipv4(pkt) != 0U) {
|
|
NET_DBG("DROP: invalid chksum");
|
|
goto drop;
|
|
}
|
|
|
|
net_pkt_set_ipv4_ttl(pkt, hdr->ttl);
|
|
|
|
net_pkt_set_family(pkt, PF_INET);
|
|
|
|
if (!net_pkt_filter_ip_recv_ok(pkt)) {
|
|
/* drop the packet */
|
|
return NET_DROP;
|
|
}
|
|
|
|
if ((!net_ipv4_is_my_addr((struct in_addr *)hdr->dst) &&
|
|
!net_ipv4_is_addr_mcast((struct in_addr *)hdr->dst) &&
|
|
!(hdr->proto == IPPROTO_UDP &&
|
|
(net_ipv4_addr_cmp((struct in_addr *)hdr->dst, net_ipv4_broadcast_address()) ||
|
|
/* RFC 1122 ch. 3.3.6 The 0.0.0.0 is non-standard bcast addr */
|
|
(IS_ENABLED(CONFIG_NET_IPV4_ACCEPT_ZERO_BROADCAST) &&
|
|
net_ipv4_addr_cmp((struct in_addr *)hdr->dst,
|
|
net_ipv4_unspecified_address())) ||
|
|
net_dhcpv4_accept_unicast(pkt)))) ||
|
|
(hdr->proto == IPPROTO_TCP &&
|
|
net_ipv4_is_addr_bcast(net_pkt_iface(pkt), (struct in_addr *)hdr->dst))) {
|
|
NET_DBG("DROP: not for me");
|
|
goto drop;
|
|
}
|
|
|
|
net_pkt_acknowledge_data(pkt, &ipv4_access);
|
|
|
|
if (opts_len) {
|
|
/* Only few options are handled in EchoRequest, rest skipped */
|
|
if (net_pkt_skip(pkt, opts_len)) {
|
|
NET_DBG("Header too big? %u", hdr_len);
|
|
goto drop;
|
|
}
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_NET_IPV4_FRAGMENT)) {
|
|
/* Check if this is a fragmented packet, and if so, handle reassembly */
|
|
if ((ntohs(*((uint16_t *)&hdr->offset[0])) &
|
|
(NET_IPV4_FRAGH_OFFSET_MASK | NET_IPV4_MORE_FRAG_MASK)) != 0) {
|
|
return net_ipv4_handle_fragment_hdr(pkt, hdr);
|
|
}
|
|
}
|
|
|
|
NET_DBG("IPv4 packet received from %s to %s",
|
|
net_sprint_ipv4_addr(&hdr->src),
|
|
net_sprint_ipv4_addr(&hdr->dst));
|
|
|
|
switch (hdr->proto) {
|
|
case IPPROTO_ICMP:
|
|
verdict = net_icmpv4_input(pkt, hdr);
|
|
if (verdict == NET_DROP) {
|
|
goto drop;
|
|
}
|
|
return verdict;
|
|
#if defined(CONFIG_NET_IPV4_IGMP)
|
|
case IPPROTO_IGMP:
|
|
verdict = net_ipv4_igmp_input(pkt, hdr);
|
|
if (verdict == NET_DROP) {
|
|
goto drop;
|
|
}
|
|
return verdict;
|
|
#endif
|
|
case IPPROTO_TCP:
|
|
proto_hdr.tcp = net_tcp_input(pkt, &tcp_access);
|
|
if (proto_hdr.tcp) {
|
|
verdict = NET_OK;
|
|
}
|
|
break;
|
|
case IPPROTO_UDP:
|
|
proto_hdr.udp = net_udp_input(pkt, &udp_access);
|
|
if (proto_hdr.udp) {
|
|
verdict = NET_OK;
|
|
}
|
|
break;
|
|
|
|
#if defined(CONFIG_NET_L2_VIRTUAL)
|
|
case IPPROTO_IPV6:
|
|
case IPPROTO_IPIP: {
|
|
struct net_addr remote_addr;
|
|
|
|
remote_addr.family = AF_INET;
|
|
net_ipv4_addr_copy_raw((uint8_t *)&remote_addr.in_addr, hdr->src);
|
|
|
|
/* Get rid of the old IP header */
|
|
net_pkt_cursor_restore(pkt, &hdr_start);
|
|
net_pkt_pull(pkt, net_pkt_ip_hdr_len(pkt) +
|
|
net_pkt_ipv4_opts_len(pkt));
|
|
|
|
return net_virtual_input(net_pkt_iface(pkt), &remote_addr,
|
|
pkt);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if (verdict == NET_DROP) {
|
|
goto drop;
|
|
}
|
|
|
|
ip.ipv4 = hdr;
|
|
|
|
verdict = net_conn_input(pkt, &ip, hdr->proto, &proto_hdr);
|
|
if (verdict != NET_DROP) {
|
|
return verdict;
|
|
}
|
|
|
|
drop:
|
|
net_stats_update_ipv4_drop(net_pkt_iface(pkt));
|
|
return NET_DROP;
|
|
}
|
|
|
|
void net_ipv4_init(void)
|
|
{
|
|
if (IS_ENABLED(CONFIG_NET_IPV4_FRAGMENT)) {
|
|
net_ipv4_setup_fragment_buffers();
|
|
}
|
|
}
|