491 lines
12 KiB
C
491 lines
12 KiB
C
/** @file
|
|
* @brief Network initialization
|
|
*
|
|
* Initialize the network IP stack. Create two fibers, one for reading data
|
|
* from applications (Tx fiber) and one for reading data from IP stack
|
|
* and passing that data to applications (Rx fiber).
|
|
*/
|
|
|
|
/*
|
|
* Copyright (c) 2016 Intel Corporation
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#if defined(CONFIG_NET_DEBUG_CORE)
|
|
#define SYS_LOG_DOMAIN "net/core"
|
|
#define NET_DEBUG 1
|
|
#endif
|
|
|
|
#include <init.h>
|
|
#include <nanokernel.h>
|
|
#include <toolchain.h>
|
|
#include <sections.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
|
|
#include <net/net_if.h>
|
|
#include <net/arp.h>
|
|
#include <net/nbuf.h>
|
|
#include <net/net_core.h>
|
|
#include <net/net_stats.h>
|
|
|
|
#include "net_private.h"
|
|
|
|
#include "icmpv6.h"
|
|
#include "ipv6.h"
|
|
|
|
#if defined(CONFIG_NET_IPV4)
|
|
#include "icmpv4.h"
|
|
#endif
|
|
|
|
#include "route.h"
|
|
#include "connection.h"
|
|
#include "udp.h"
|
|
|
|
/* Stack for the rx fiber.
|
|
*/
|
|
#if !defined(CONFIG_NET_RX_STACK_SIZE)
|
|
#define CONFIG_NET_RX_STACK_SIZE 1024
|
|
#endif
|
|
static char __noinit __stack rx_fiber_stack[CONFIG_NET_RX_STACK_SIZE];
|
|
static struct nano_fifo rx_queue;
|
|
static nano_thread_id_t rx_fiber_id;
|
|
|
|
#if defined(CONFIG_NET_STATISTICS)
|
|
#define PRINT(fmt, ...) NET_INFO(fmt, ##__VA_ARGS__)
|
|
struct net_stats net_stats;
|
|
#define GET_STAT(s) net_stats.s
|
|
#define PRINT_STATISTICS_INTERVAL (30 * sys_clock_ticks_per_sec)
|
|
#define net_print_statistics stats /* to make the debug print line shorter */
|
|
|
|
static inline void stats(void)
|
|
{
|
|
static uint32_t next_print;
|
|
uint32_t curr = sys_tick_get_32();
|
|
|
|
if (!next_print || (next_print < curr &&
|
|
(!((curr - next_print) > PRINT_STATISTICS_INTERVAL)))) {
|
|
uint32_t new_print;
|
|
|
|
#if defined(CONFIG_NET_IPV6)
|
|
PRINT("IPv6 recv %d\tsent\t%d\tdrop\t%d\tforwarded\t%d",
|
|
GET_STAT(ipv6.recv),
|
|
GET_STAT(ipv6.sent),
|
|
GET_STAT(ipv6.drop),
|
|
GET_STAT(ipv6.forwarded));
|
|
#if defined(CONFIG_NET_IPV6_ND)
|
|
PRINT("IPv6 ND recv %d\tsent\t%d\tdrop\t%d",
|
|
GET_STAT(ipv6_nd.recv),
|
|
GET_STAT(ipv6_nd.sent),
|
|
GET_STAT(ipv6_nd.drop));
|
|
#endif /* CONFIG_NET_IPV6_ND */
|
|
#endif /* CONFIG_NET_IPV6 */
|
|
|
|
#if defined(CONFIG_NET_IPV4)
|
|
PRINT("IPv4 recv %d\tsent\t%d\tdrop\t%d\tforwarded\t%d",
|
|
GET_STAT(ipv4.recv),
|
|
GET_STAT(ipv4.sent),
|
|
GET_STAT(ipv4.drop),
|
|
GET_STAT(ipv4.forwarded));
|
|
#endif /* CONFIG_NET_IPV4 */
|
|
|
|
PRINT("IP vhlerr %d\thblener\t%d\tlblener\t%d",
|
|
GET_STAT(ip_errors.vhlerr),
|
|
GET_STAT(ip_errors.hblenerr),
|
|
GET_STAT(ip_errors.lblenerr));
|
|
PRINT("IP fragerr %d\tchkerr\t%d\tprotoer\t%d",
|
|
GET_STAT(ip_errors.fragerr),
|
|
GET_STAT(ip_errors.chkerr),
|
|
GET_STAT(ip_errors.protoerr));
|
|
|
|
PRINT("ICMP recv %d\tsent\t%d\tdrop\t%d",
|
|
GET_STAT(icmp.recv),
|
|
GET_STAT(icmp.sent),
|
|
GET_STAT(icmp.drop));
|
|
PRINT("ICMP typeer %d\tchkerr\t%d",
|
|
GET_STAT(icmp.typeerr),
|
|
GET_STAT(icmp.chkerr));
|
|
|
|
#if defined(CONFIG_NET_UDP)
|
|
PRINT("UDP recv %d\tsent\t%d\tdrop\t%d",
|
|
GET_STAT(udp.recv),
|
|
GET_STAT(udp.sent),
|
|
GET_STAT(udp.drop));
|
|
PRINT("UDP chkerr %d",
|
|
GET_STAT(udp.chkerr));
|
|
#endif
|
|
|
|
PRINT("Processing err %d", GET_STAT(processing_error));
|
|
|
|
new_print = curr + PRINT_STATISTICS_INTERVAL;
|
|
if (new_print > curr) {
|
|
next_print = new_print;
|
|
} else {
|
|
/* Overflow */
|
|
next_print = PRINT_STATISTICS_INTERVAL -
|
|
(0xffffffff - curr);
|
|
}
|
|
}
|
|
}
|
|
#else /* CONFIG_NET_STATISTICS */
|
|
#define net_print_statistics()
|
|
#endif /* CONFIG_NET_STATISTICS */
|
|
|
|
#if defined(CONFIG_NET_IPV6)
|
|
static inline enum net_verdict process_icmpv6_pkt(struct net_buf *buf,
|
|
struct net_ipv6_hdr *ipv6)
|
|
{
|
|
struct net_icmp_hdr *hdr = NET_ICMP_BUF(buf);
|
|
uint16_t len = (ipv6->len[0] << 8) + ipv6->len[1];
|
|
|
|
NET_DBG("ICMPv6 packet received length %d type %d code %d",
|
|
len, hdr->type, hdr->code);
|
|
|
|
return net_icmpv6_input(buf, len, hdr->type, hdr->code);
|
|
}
|
|
|
|
static inline enum net_verdict process_ipv6_pkt(struct net_buf *buf)
|
|
{
|
|
struct net_ipv6_hdr *hdr = NET_IPV6_BUF(buf);
|
|
int real_len = net_buf_frags_len(buf);
|
|
int pkt_len = (hdr->len[0] << 8) + hdr->len[1] + sizeof(*hdr);
|
|
|
|
if (real_len > pkt_len) {
|
|
NET_DBG("IPv6 adjust pkt len to %d (was %d)",
|
|
pkt_len, real_len);
|
|
net_buf_frag_last(buf)->len -= real_len - pkt_len;
|
|
real_len -= pkt_len;
|
|
} else if (real_len < pkt_len) {
|
|
NET_DBG("IPv6 packet size %d buf len %d", pkt_len, real_len);
|
|
NET_STATS_IPV6(++net_stats.ipv6.drop);
|
|
goto drop;
|
|
}
|
|
|
|
#if NET_DEBUG > 0
|
|
do {
|
|
char out[sizeof("xxxx:xxxx:xxxx:xxxx:xxxx:xxxx")];
|
|
snprintf(out, sizeof(out), net_sprint_ipv6_addr(&hdr->dst));
|
|
NET_DBG("IPv6 packet len %d received from %s to %s",
|
|
real_len, net_sprint_ipv6_addr(&hdr->src), out);
|
|
} while (0);
|
|
#endif /* NET_DEBUG > 0 */
|
|
|
|
if (net_is_ipv6_addr_mcast(&hdr->src)) {
|
|
NET_STATS_IPV6(++net_stats.ipv6.drop);
|
|
NET_DBG("Dropping src multicast packet");
|
|
goto drop;
|
|
}
|
|
|
|
if (!net_is_my_ipv6_addr(&hdr->dst) &&
|
|
!net_is_my_ipv6_maddr(&hdr->dst) &&
|
|
!net_is_ipv6_addr_mcast(&hdr->dst) &&
|
|
!net_is_ipv6_addr_loopback(&hdr->dst)) {
|
|
NET_DBG("IPv6 packet in buf %p not for me", buf);
|
|
NET_STATS_IPV6(++net_stats.ipv6.drop);
|
|
goto drop;
|
|
}
|
|
|
|
/* Check extension headers */
|
|
net_nbuf_set_next_hdr(buf, &hdr->nexthdr);
|
|
net_nbuf_set_ext_len(buf, 0);
|
|
net_nbuf_set_ext_bitmap(buf, 0);
|
|
|
|
while (1) {
|
|
switch (*(net_nbuf_next_hdr(buf))) {
|
|
|
|
case IPPROTO_ICMPV6:
|
|
net_nbuf_set_ip_hdr_len(buf,
|
|
sizeof(struct net_ipv6_hdr));
|
|
return process_icmpv6_pkt(buf, hdr);
|
|
|
|
case IPPROTO_UDP:
|
|
net_nbuf_set_ip_hdr_len(buf,
|
|
sizeof(struct net_ipv6_hdr));
|
|
return net_conn_input(IPPROTO_UDP, buf);
|
|
|
|
default:
|
|
NET_DBG("Unknown next header type");
|
|
goto drop;
|
|
}
|
|
}
|
|
|
|
drop:
|
|
return NET_DROP;
|
|
}
|
|
#endif /* CONFIG_NET_IPV6 */
|
|
|
|
#if defined(CONFIG_NET_IPV4)
|
|
static inline enum net_verdict process_icmpv4_pkt(struct net_buf *buf,
|
|
struct net_ipv4_hdr *ipv4)
|
|
{
|
|
struct net_icmp_hdr *hdr = NET_ICMP_BUF(buf);
|
|
uint16_t len = (ipv4->len[0] << 8) + ipv4->len[1];
|
|
|
|
NET_DBG("ICMPv4 packet received length %d type %d code %d",
|
|
len, hdr->type, hdr->code);
|
|
|
|
return net_icmpv4_input(buf, len, hdr->type, hdr->code);
|
|
}
|
|
#endif /* CONFIG_NET_IPV4 */
|
|
|
|
#if defined(CONFIG_NET_IPV4)
|
|
static inline enum net_verdict process_ipv4_pkt(struct net_buf *buf)
|
|
{
|
|
struct net_ipv4_hdr *hdr = NET_IPV4_BUF(buf);
|
|
int real_len = net_buf_frags_len(buf);
|
|
int pkt_len = (hdr->len[0] << 8) + hdr->len[1];
|
|
enum net_verdict verdict = NET_DROP;
|
|
|
|
if (real_len > pkt_len) {
|
|
NET_DBG("IPv4 adjust pkt len to %d (was %d)",
|
|
pkt_len, real_len);
|
|
net_buf_frag_last(buf)->len -= real_len - pkt_len;
|
|
real_len -= pkt_len;
|
|
} else if (real_len < pkt_len) {
|
|
NET_DBG("IPv4 packet size %d buf len %d", pkt_len, real_len);
|
|
goto drop;
|
|
}
|
|
|
|
#if NET_DEBUG > 0
|
|
do {
|
|
char out[sizeof("xxx.xxx.xxx.xxx")];
|
|
snprintf(out, sizeof(out), net_sprint_ipv4_addr(&hdr->dst));
|
|
NET_DBG("IPv4 packet received from %s to %s",
|
|
net_sprint_ipv4_addr(&hdr->src), out);
|
|
} while (0);
|
|
#endif /* NET_DEBUG > 0 */
|
|
|
|
net_nbuf_set_ip_hdr_len(buf, sizeof(struct net_ipv4_hdr));
|
|
|
|
if (!net_is_my_ipv4_addr(&hdr->dst)) {
|
|
#if defined(CONFIG_NET_DHCPV4)
|
|
if (hdr->proto == IPPROTO_UDP &&
|
|
net_ipv4_addr_cmp(&hdr->dst,
|
|
net_ipv4_broadcast_address())) {
|
|
|
|
verdict = net_conn_input(IPPROTO_UDP, buf);
|
|
if (verdict != NET_DROP) {
|
|
return verdict;
|
|
}
|
|
}
|
|
#endif
|
|
NET_DBG("IPv4 packet in buf %p not for me", buf);
|
|
goto drop;
|
|
}
|
|
|
|
switch (hdr->proto) {
|
|
case IPPROTO_ICMP:
|
|
verdict = process_icmpv4_pkt(buf, hdr);
|
|
break;
|
|
case IPPROTO_UDP:
|
|
verdict = net_conn_input(IPPROTO_UDP, buf);
|
|
break;
|
|
}
|
|
|
|
if (verdict != NET_DROP) {
|
|
return verdict;
|
|
}
|
|
|
|
drop:
|
|
NET_STATS(++net_stats.ipv4.drop);
|
|
return NET_DROP;
|
|
}
|
|
#endif /* CONFIG_NET_IPV4 */
|
|
|
|
static inline enum net_verdict process_data(struct net_buf *buf)
|
|
{
|
|
int ret;
|
|
|
|
/* If there is no data, then drop the packet. Also if
|
|
* the buffer is wrong type, then also drop the packet.
|
|
* The first buffer needs to have user data part that
|
|
* contains user data. The rest of the fragments should
|
|
* be data fragments without user data.
|
|
*/
|
|
if (!buf->frags || !buf->user_data_size) {
|
|
NET_DBG("Corrupted buffer");
|
|
NET_STATS(++net_stats.processing_error);
|
|
|
|
return NET_DROP;
|
|
}
|
|
|
|
ret = net_if_recv_data(net_nbuf_iface(buf), buf);
|
|
if (ret != NET_CONTINUE) {
|
|
if (ret == NET_DROP) {
|
|
NET_DBG("Buffer disgarded by L2");
|
|
NET_STATS(++net_stats.processing_error);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* IP version and header length. */
|
|
switch (NET_IPV6_BUF(buf)->vtc & 0xf0) {
|
|
#if defined(CONFIG_NET_IPV6)
|
|
case 0x60:
|
|
NET_STATS_IPV6(++net_stats.ipv6.recv);
|
|
net_nbuf_set_family(buf, PF_INET6);
|
|
return process_ipv6_pkt(buf);
|
|
#endif
|
|
#if defined(CONFIG_NET_IPV4)
|
|
case 0x40:
|
|
NET_STATS_IPV4(++net_stats.ipv4.recv);
|
|
net_nbuf_set_family(buf, PF_INET);
|
|
return process_ipv4_pkt(buf);
|
|
#endif
|
|
}
|
|
|
|
NET_DBG("Unknown IP family packet");
|
|
NET_STATS(++net_stats.ip_errors.protoerr);
|
|
NET_STATS(++net_stats.ip_errors.vhlerr);
|
|
|
|
return NET_DROP;
|
|
}
|
|
|
|
static void net_rx_fiber(void)
|
|
{
|
|
struct net_buf *buf;
|
|
|
|
NET_DBG("Starting RX fiber (stack %d bytes)",
|
|
sizeof(rx_fiber_stack));
|
|
|
|
/* Starting TX side. The ordering is important here and the TX
|
|
* can only be started when RX side is ready to receive packets.
|
|
*/
|
|
net_if_init();
|
|
|
|
while (1) {
|
|
buf = net_buf_get_timeout(&rx_queue, 0, TICKS_UNLIMITED);
|
|
|
|
net_analyze_stack("RX fiber", rx_fiber_stack,
|
|
sizeof(rx_fiber_stack));
|
|
|
|
NET_DBG("Received buf %p len %d", buf,
|
|
net_buf_frags_len(buf));
|
|
|
|
switch (process_data(buf)) {
|
|
case NET_OK:
|
|
NET_DBG("Consumed buf %p", buf);
|
|
break;
|
|
case NET_DROP:
|
|
default:
|
|
NET_DBG("Dropping buf %p", buf);
|
|
net_nbuf_unref(buf);
|
|
break;
|
|
}
|
|
|
|
net_print_statistics();
|
|
net_nbuf_print();
|
|
|
|
fiber_yield();
|
|
}
|
|
}
|
|
|
|
static void init_rx_queue(void)
|
|
{
|
|
nano_fifo_init(&rx_queue);
|
|
|
|
rx_fiber_id = fiber_start(rx_fiber_stack, sizeof(rx_fiber_stack),
|
|
(nano_fiber_entry_t)net_rx_fiber,
|
|
0, 0, 8, 0);
|
|
}
|
|
|
|
/* Called when data needs to be sent to network */
|
|
int net_send_data(struct net_buf *buf)
|
|
{
|
|
if (!buf || !buf->frags) {
|
|
return -ENODATA;
|
|
}
|
|
|
|
if (!net_nbuf_iface(buf)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
#if defined(CONFIG_NET_STATISTICS)
|
|
switch (net_nbuf_family(buf)) {
|
|
case AF_INET:
|
|
NET_STATS_IPV4(++net_stats.ipv4.sent);
|
|
break;
|
|
case AF_INET6:
|
|
NET_STATS_IPV6(++net_stats.ipv6.sent);
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
if (net_if_send_data(net_nbuf_iface(buf), buf) == NET_DROP) {
|
|
return -EIO;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Called by driver when an IP packet has been received */
|
|
int net_recv_data(struct net_if *iface, struct net_buf *buf)
|
|
{
|
|
if (!buf->frags) {
|
|
return -ENODATA;
|
|
}
|
|
|
|
NET_DBG("fifo %p iface %p buf %p len %d", &rx_queue, iface, buf,
|
|
net_buf_frags_len(buf));
|
|
|
|
net_nbuf_set_iface(buf, iface);
|
|
|
|
net_buf_put(&rx_queue, buf);
|
|
|
|
fiber_wakeup(rx_fiber_id);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline void l3_init(void)
|
|
{
|
|
net_icmpv6_init();
|
|
net_ipv6_init();
|
|
|
|
#if defined(CONFIG_NET_UDP) || defined(CONFIG_NET_TCP)
|
|
net_conn_init();
|
|
#endif
|
|
net_udp_init();
|
|
|
|
net_route_init();
|
|
|
|
NET_DBG("Network L3 init done");
|
|
}
|
|
|
|
static inline void l2_init(void)
|
|
{
|
|
net_arp_init();
|
|
|
|
NET_DBG("Network L2 init done");
|
|
}
|
|
|
|
static int net_init(struct device *unused)
|
|
{
|
|
NET_DBG("Priority %d", CONFIG_NET_INIT_PRIO);
|
|
|
|
net_nbuf_init();
|
|
|
|
net_context_init();
|
|
|
|
l2_init();
|
|
l3_init();
|
|
|
|
init_rx_queue();
|
|
|
|
return 0;
|
|
}
|
|
|
|
SYS_INIT(net_init, NANOKERNEL, CONFIG_NET_INIT_PRIO);
|