/** @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) 2015 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. */ #define DEBUG DEBUG_PRINT #include "contiki/ip/uip-debug.h" #include #include #include #include #include #include #include #include #include #include #include "net_driver_15_4.h" #include "net_driver_slip.h" #include "net_driver_ethernet.h" #include "contiki/os/sys/process.h" #include "contiki/os/sys/etimer.h" #include "contiki/os/sys/ctimer.h" #include "contiki/netstack.h" #include "contiki/ipv6/uip-ds6.h" #include "contiki/ip/simple-udp.h" #include "contiki/os/dev/slip.h" #ifdef CONFIG_15_4_BEACON_SUPPORT #include "contiki/mac/handler-802154.h" #endif /* Declare some private functions only to be used in this file so the * prototypes are not found in .h file. */ struct nano_fifo *net_context_get_queue(struct net_context *context); struct simple_udp_connection * net_context_get_udp_connection(struct net_context *context); int net_context_get_receiver_registered(struct net_context *context); void net_context_set_receiver_registered(struct net_context *context); /* Stacks for the tx & rx fibers. * FIXME: stack size needs fine-tuning */ #define STACKSIZE_UNIT 1024 static char __noinit rx_fiber_stack[STACKSIZE_UNIT * 1]; static char __noinit tx_fiber_stack[STACKSIZE_UNIT * 1]; static char __noinit timer_fiber_stack[STACKSIZE_UNIT * 3 / 2]; static struct net_dev { /* Queue for incoming packets from driver */ struct nano_fifo rx_queue; /* Queue for outgoing packets from apps */ struct nano_fifo tx_queue; /* Registered network driver */ struct net_driver *drv; } netdev; /* Called by application to send a packet */ int net_send(struct net_buf *buf) { if (ip_buf_len(buf) == 0) { return -ENODATA; } nano_fifo_put(&netdev.tx_queue, buf); return 0; } #ifdef CONFIG_NETWORKING_STATISTICS #define STAT(s) uip_stat.s #define PRINT_STATISTICS_INTERVAL (10 * sys_clock_ticks_per_sec) #define net_print_statistics stats /* to make the debug print line shorter */ #if NET_MAC_CONF_STATS #include "mac/mac.h" #endif #if RPL_CONF_STATS #include "rpl/rpl-private.h" #endif #if NET_COAP_CONF_STATS #include "er-coap/er-coap.h" #endif #if HANDLER_802154_CONF_STATS #include "mac/handler-802154.h" #endif static void stats(void) { static clock_time_t last_print; /* See contiki/ip/uip.h for descriptions of the different values */ if (clock_time() > (last_print + PRINT_STATISTICS_INTERVAL)) { #if NET_MAC_CONF_STATS #define MAC_STAT(s) (net_mac_stats.s) NET_DBG("L2 bytes recv %d\tsent\t%d\n", MAC_STAT(bytes_received), MAC_STAT(bytes_sent)); #endif NET_DBG("IP recv %d\tsent\t%d\tdrop\t%d\tforwarded\t%d\n", STAT(ip.recv), STAT(ip.sent), STAT(ip.drop), STAT(ip.forwarded)); NET_DBG("IP vhlerr %d\thblener\t%d\tlblener\t%d\n", STAT(ip.vhlerr), STAT(ip.hblenerr), STAT(ip.lblenerr)); NET_DBG("IP fragerr %d\tchkerr\t%d\tprotoer\t%d\n", STAT(ip.fragerr), STAT(ip.chkerr), STAT(ip.protoerr)); NET_DBG("ICMP recv %d\tsent\t%d\tdrop\t%d\n", STAT(icmp.recv), STAT(icmp.sent), STAT(icmp.drop)); NET_DBG("ICMP typeer %d\tchkerr\t%d\n", STAT(icmp.typeerr), STAT(icmp.chkerr)); NET_DBG("UDP recv %d\tsent\t%d\tdrop\t%d\n", STAT(udp.recv), STAT(udp.sent), STAT(udp.drop)); NET_DBG("UDP chkerr %d\n", STAT(icmp.chkerr)); #if NET_COAP_CONF_STATS NET_DBG("CoAP recv %d\terr\t%d\tsent\t%d\tre-sent\t%d\n", NET_COAP_STAT(recv), NET_COAP_STAT(recv_err), NET_COAP_STAT(sent), NET_COAP_STAT(re_sent)); #endif #if NETSTACK_CONF_WITH_IPV6 NET_DBG("ND recv %d\tsent\t%d\tdrop\t%d\n", STAT(nd6.recv), STAT(nd6.sent), STAT(nd6.drop)); #endif #if RPL_CONF_STATS #define RSTAT(s) RPL_STAT(rpl_stats.s) NET_DBG("RPL overflows %d\tl-repairs\t%d\tg-repairs\t%d\n", RSTAT(mem_overflows), RSTAT(local_repairs), RSTAT(global_repairs)); NET_DBG("RPL malformed %d\tresets \t%d\tp-switch\t%d\n", RSTAT(malformed_msgs), RSTAT(resets), RSTAT(parent_switch)); NET_DBG("RPL f-errors %d\tl-errors\t%d\tl-warnings\t%d\n", RSTAT(forward_errors), RSTAT(loop_errors), RSTAT(loop_warnings)); NET_DBG("RPL r-repairs %d\n", RSTAT(root_repairs)); #endif #if HANDLER_802154_CONF_STATS #define IEEE802154_STAT(s) (handler_802154_stats.s) NET_DBG("802.15.4 beacons recv\t%d\tsent\t%d\treqs sent\t%d\n", IEEE802154_STAT(beacons_received), IEEE802154_STAT(beacons_sent), IEEE802154_STAT(beacons_reqs_sent)); #endif last_print = clock_time(); } } #else #define net_print_statistics() #endif /* Switch the ports and addresses and set route and neighbor cache. * Returns 1 if packet was sent properly, in this case it is the caller * that needs to release the net_buf. If 0 is returned, then uIP stack * has released the net_buf already because there was an some net related * error when sending the buffer. */ static inline int udp_prepare_and_send(struct net_context *context, struct net_buf *buf) { #ifdef CONFIG_NETWORKING_IPV6_NO_ND uip_ds6_route_t *route_old, *route_new = NULL; uip_ds6_nbr_t *nbr; #endif uip_ipaddr_t tmp; uint16_t port; uint8_t ret; if (uip_len(buf) == 0) { /* This is expected as uIP will typically set the * packet length to 0 after receiving it. So we need * to fix the length here. The protocol specific * part is added also here. */ uip_len(buf) = ip_buf_len(buf); } ip_buf_appdata(buf) = &uip_buf(buf)[UIP_IPUDPH_LEN]; port = NET_BUF_UDP(buf)->srcport; NET_BUF_UDP(buf)->srcport = NET_BUF_UDP(buf)->destport; NET_BUF_UDP(buf)->destport = port; uip_ipaddr_copy(&tmp, &NET_BUF_IP(buf)->srcipaddr); uip_ipaddr_copy(&NET_BUF_IP(buf)->srcipaddr, &NET_BUF_IP(buf)->destipaddr); uip_ipaddr_copy(&NET_BUF_IP(buf)->destipaddr, &tmp); #ifdef CONFIG_NETWORKING_IPV6_NO_ND /* The peer needs to be in neighbor cache before route can be added. */ nbr = uip_ds6_nbr_lookup((uip_ipaddr_t *)&NET_BUF_IP(buf)->destipaddr); if (!nbr) { const uip_lladdr_t *lladdr = (const uip_lladdr_t *)&ip_buf_ll_src(buf); nbr = uip_ds6_nbr_add( (uip_ipaddr_t *)&NET_BUF_IP(buf)->destipaddr, lladdr, 0, NBR_REACHABLE); if (!nbr) { NET_DBG("Cannot add peer "); PRINT6ADDR(&NET_BUF_IP(buf)->destipaddr); PRINT(" to neighbor cache\n"); } } /* Temporarily add route to peer, delete the route after * sending the packet. Check if there was already a * route and do not remove it if there was existing * route to this peer. */ route_old = uip_ds6_route_lookup(&NET_BUF_IP(buf)->destipaddr); if (!route_old) { route_new = uip_ds6_route_add(&NET_BUF_IP(buf)->destipaddr, 128, &NET_BUF_IP(buf)->destipaddr); if (!route_new) { NET_DBG("Cannot add route to peer "); PRINT6ADDR(&NET_BUF_IP(buf)->destipaddr); PRINT("\n"); } } #endif ret = simple_udp_sendto_port(buf, net_context_get_udp_connection(context), ip_buf_appdata(buf), ip_buf_appdatalen(buf), &NET_BUF_IP(buf)->destipaddr, uip_ntohs(NET_BUF_UDP(buf)->destport)); if (!ret) { NET_DBG("Packet could not be sent properly.\n"); } #ifdef CONFIG_NETWORKING_IPV6_NO_ND if (!route_old && route_new) { /* This will also remove the neighbor cache entry */ uip_ds6_route_rm(route_new); } #endif return !ret; } /* Application wants to send a reply */ int net_reply(struct net_context *context, struct net_buf *buf) { struct net_tuple *tuple; struct uip_udp_conn *udp; int ret = 0; if (!context || !buf) { return -EINVAL; } tuple = net_context_get_tuple(context); if (!tuple) { return -ENOENT; } switch (tuple->ip_proto) { case IPPROTO_UDP: udp = uip_udp_conn(buf); if (!udp) { NET_ERR("UDP connection missing\n"); return -ESRCH; } ret = udp_prepare_and_send(context, buf); break; case IPPROTO_TCP: NET_DBG("TCP not yet supported\n"); return -EINVAL; case IPPROTO_ICMPV6: NET_DBG("ICMPv6 not yet supported\n"); return -EINVAL; } return ret; } /* Called by driver when an IP packet has been received */ int net_recv(struct net_buf *buf) { if (ip_buf_len(buf) == 0) { return -ENODATA; } nano_fifo_put(&netdev.rx_queue, buf); return 0; } static void udp_packet_receive(struct simple_udp_connection *c, const uip_ipaddr_t *source_addr, uint16_t source_port, const uip_ipaddr_t *dest_addr, uint16_t dest_port, const uint8_t *data, uint16_t datalen, void *user_data, struct net_buf *buf) { struct net_context *context = user_data; if (!context) { /* If the context is not there, then we must discard * the buffer here, otherwise we have a buffer leak. */ ip_buf_unref(buf); return; } ip_buf_appdatalen(buf) = datalen; ip_buf_appdata(buf) = &uip_buf(buf)[UIP_IPUDPH_LEN]; NET_DBG("packet received context %p len %d " "appdata %p appdatalen %d\n", context, ip_buf_len(buf), ip_buf_appdata(buf), ip_buf_appdatalen(buf)); nano_fifo_put(net_context_get_queue(context), buf); } #ifdef CONFIG_NANO_TIMEOUTS static inline struct net_buf *buf_wait_timeout(struct nano_fifo *queue, int32_t timeout) { switch (sys_execution_context_type_get()) { case NANO_CTX_FIBER: return nano_fiber_fifo_get_wait_timeout(queue, timeout); case NANO_CTX_TASK: return nano_task_fifo_get_wait_timeout(queue, timeout); case NANO_CTX_ISR: default: /* Invalid context type */ break; } return NULL; } #endif /* Called by application when it wants to receive network data */ struct net_buf *net_receive(struct net_context *context, int32_t timeout) { struct nano_fifo *rx_queue = net_context_get_queue(context); struct net_buf *buf; struct net_tuple *tuple; int ret = 0; uint16_t reserve = 0; tuple = net_context_get_tuple(context); if (!tuple) { return NULL; } switch (tuple->ip_proto) { case IPPROTO_UDP: if (!net_context_get_receiver_registered(context)) { struct simple_udp_connection *udp = net_context_get_udp_connection(context); ret = simple_udp_register(udp, tuple->local_port, #ifdef CONFIG_NETWORKING_WITH_IPV6 (uip_ip6addr_t *)&tuple->remote_addr->in6_addr, #else (uip_ip4addr_t *)&tuple->remote_addr->in_addr, #endif tuple->remote_port, udp_packet_receive, context); if (!ret) { NET_DBG("UDP connection listener failed\n"); ret = -ENOENT; break; } } net_context_set_receiver_registered(context); ret = 0; reserve = UIP_IPUDPH_LEN; break; case IPPROTO_TCP: NET_DBG("TCP not yet supported\n"); ret = -EINVAL; break; case IPPROTO_ICMPV6: NET_DBG("ICMPv6 not yet supported\n"); ret = -EINVAL; break; default: NET_ERR("Invalid IP protocol. " "Internal data structure corrupted!\n"); ret = -EINVAL; break; } if (ret) { return NULL; } switch (timeout) { case TICKS_UNLIMITED: buf = nano_fifo_get_wait(rx_queue); break; case TICKS_NONE: buf = nano_fifo_get(rx_queue); break; default: #ifdef CONFIG_NANO_TIMEOUTS buf = buf_wait_timeout(rx_queue, timeout); #else /* CONFIG_NANO_TIMEOUTS */ buf = nano_fifo_get(rx_queue); #endif break; } if (buf && reserve) { ip_buf_appdatalen(buf) = ip_buf_len(buf) - reserve; ip_buf_appdata(buf) = &uip_buf(buf)[reserve]; } return buf; } static void udp_packet_reply(struct simple_udp_connection *c, const uip_ipaddr_t *source_addr, uint16_t source_port, const uip_ipaddr_t *dest_addr, uint16_t dest_port, const uint8_t *data, uint16_t datalen, void *user_data, struct net_buf *buf) { struct net_context *context = user_data; struct nano_fifo *queue; if (!context) { /* If the context is not there, then we must discard * the buffer here, otherwise we have a buffer leak. */ ip_buf_unref(buf); return; } queue = net_context_get_queue(context); /* Contiki stack will overwrite the uip_len(buf) and * uip_appdatalen(buf) values, so in order to allow * the application to use them, copy the values here. */ ip_buf_appdatalen(buf) = datalen; NET_DBG("packet reply context %p len %d " "appdata %p appdatalen %d queue %p\n", context, ip_buf_len(buf), ip_buf_appdata(buf), ip_buf_appdatalen(buf), queue); nano_fifo_put(queue, buf); } /* Internal function to send network data to uIP stack */ static int check_and_send_packet(struct net_buf *buf) { struct net_tuple *tuple; struct simple_udp_connection *udp; int ret = 0; if (!netdev.drv) { return -EINVAL; } tuple = net_context_get_tuple(ip_buf_context(buf)); if (!tuple) { return -EINVAL; } switch (tuple->ip_proto) { case IPPROTO_UDP: udp = net_context_get_udp_connection(ip_buf_context(buf)); if (!net_context_get_receiver_registered(ip_buf_context(buf))) { ret = simple_udp_register(udp, tuple->local_port, #ifdef CONFIG_NETWORKING_WITH_IPV6 (uip_ip6addr_t *)&tuple->remote_addr->in6_addr, #else (uip_ip4addr_t *)&tuple->remote_addr->in_addr, #endif tuple->remote_port, udp_packet_reply, ip_buf_context(buf)); if (!ret) { NET_DBG("UDP connection creation failed\n"); ret = -ENOENT; break; } net_context_set_receiver_registered(ip_buf_context(buf)); } if (ip_buf_appdatalen(buf) == 0) { /* User application has not set the application data * length. The buffer will be discarded if we do not * set the value correctly. */ uip_appdatalen(buf) = buf->len - UIP_IPUDPH_LEN; } ret = simple_udp_send(buf, udp, uip_appdata(buf), uip_appdatalen(buf)); break; case IPPROTO_TCP: NET_DBG("TCP not yet supported\n"); ret = -EINVAL; break; case IPPROTO_ICMPV6: NET_DBG("ICMPv6 not yet supported\n"); ret = -EINVAL; break; } return ret; } static void net_tx_fiber(void) { NET_DBG("Starting TX fiber\n"); while (1) { struct net_buf *buf; int ret; /* Get next packet from application - wait if necessary */ buf = nano_fifo_get_wait(&netdev.tx_queue); NET_DBG("Sending (buf %p, len %u) to IP stack\n", buf, buf->len); /* What to do with the buffer: * <0: error, release the buffer * 0: message was discarded by uIP, release the buffer here * >0: message was sent ok, buffer released already */ ret = check_and_send_packet(buf); if (ret < 0) { ip_buf_unref(buf); goto wait_next; } else if (ret > 0) { goto wait_next; } NET_BUF_CHECK_IF_NOT_IN_USE(buf); /* Check for any events that we might need to process */ do { ret = process_run(buf); } while (ret > 0); ip_buf_unref(buf); wait_next: /* Check stack usage (no-op if not enabled) */ net_analyze_stack("TX fiber", tx_fiber_stack, sizeof(tx_fiber_stack)); net_print_statistics(); } } static void net_rx_fiber(void) { struct net_buf *buf; NET_DBG("Starting RX fiber\n"); while (1) { buf = nano_fifo_get_wait(&netdev.rx_queue); /* Check stack usage (no-op if not enabled) */ net_analyze_stack("RX fiber", rx_fiber_stack, sizeof(rx_fiber_stack)); NET_DBG("Received buf %p\n", buf); if (!tcpip_input(buf)) { ip_buf_unref(buf); } /* The buffer is on to its way to receiver at this * point. We must not remove it here. */ net_print_statistics(); } } /* * Run various Contiki timers. At the moment this is done via polling. */ #define DEFAULT_TIMER_WAKEUP 2 #define MAX_TIMER_WAKEUP 2 static void net_timer_fiber(void) { clock_time_t next_wakeup; NET_DBG("Starting net timer fiber\n"); while (1) { /* Run various timers */ next_wakeup = etimer_request_poll(); if (next_wakeup == 0) { /* There was no timers, wait a bit */ next_wakeup = DEFAULT_TIMER_WAKEUP; } else { if (next_wakeup > MAX_TIMER_WAKEUP) { next_wakeup = MAX_TIMER_WAKEUP; } #ifdef CONFIG_INIT_STACKS { static clock_time_t last_print; /* Print stack usage every 10 sec */ if (!last_print || (last_print + 10 * sys_clock_hw_cycles_per_tick) < clock_get_cycle()) { net_analyze_stack("timer fiber", timer_fiber_stack, sizeof(timer_fiber_stack)); last_print = clock_get_cycle() + 1; } } #endif } fiber_sleep(next_wakeup); } } static void init_rx_queue(void) { nano_fifo_init(&netdev.rx_queue); fiber_start(rx_fiber_stack, sizeof(rx_fiber_stack), (nano_fiber_entry_t)net_rx_fiber, 0, 0, 7, 0); } static void init_tx_queue(void) { nano_fifo_init(&netdev.tx_queue); fiber_start(tx_fiber_stack, sizeof(tx_fiber_stack), (nano_fiber_entry_t)net_tx_fiber, 0, 0, 7, 0); } static void init_timer_fiber(void) { fiber_start(timer_fiber_stack, sizeof(timer_fiber_stack), (nano_fiber_entry_t)net_timer_fiber, 0, 0, 7, 0); } int net_set_mac(uint8_t *mac, uint8_t len) { if ((len > UIP_LLADDR_LEN) || (len != 6 && len != 8)) { NET_ERR("Wrong ll addr len, len %d, max %d\n", len, UIP_LLADDR_LEN); return -EINVAL; } linkaddr_set_node_addr((linkaddr_t *)mac); NET_DBG("MAC "); PRINTLLADDR((uip_lladdr_t *)&linkaddr_node_addr); PRINTF("\n"); #ifdef CONFIG_NETWORKING_WITH_IPV6 { uip_ds6_addr_t *lladdr; uip_ds6_set_lladdr((uip_lladdr_t *)mac); lladdr = uip_ds6_get_link_local(-1); NET_DBG("Tentative link-local IPv6 address "); PRINT6ADDR(&lladdr->ipaddr); PRINTF("\n"); lladdr->state = ADDR_AUTOCONF; } #else memcpy(&uip_lladdr, mac, len); #endif return 0; } static uint8_t net_tcpip_output(struct net_buf *buf, const uip_lladdr_t *lladdr) { int res; if (!netdev.drv) { return 0; } if (lladdr) { linkaddr_copy(&ip_buf_ll_dest(buf), (const linkaddr_t *)lladdr); } else { linkaddr_copy(&ip_buf_ll_dest(buf), &linkaddr_null); } if (ip_buf_len(buf) == 0) { return 0; } res = netdev.drv->send(buf); if (res < 0) { res = 0; } return (uint8_t)res; } static int network_initialization(void) { /* Initialize and start Contiki uIP stack */ clock_init(); rtimer_init(); ctimer_init(); process_init(); tcpip_set_outputfunc(net_tcpip_output); process_start(&tcpip_process, NULL); process_start(&simple_udp_process, NULL); process_start(&etimer_process, NULL); process_start(&ctimer_process, NULL); slip_start(); #if CONFIG_15_4_BEACON_SUPPORT && CONFIG_NETWORKING_WITH_15_4_PAN_ID handler_802154_join(CONFIG_NETWORKING_WITH_15_4_PAN_ID, 1); #endif return 0; } int net_register_driver(struct net_driver *drv) { int r; if (netdev.drv) { return -EALREADY; } if (!drv->open || !drv->send) { return -EINVAL; } r = drv->open(); if (r < 0) { return r; } netdev.drv = drv; return 0; } void net_unregister_driver(struct net_driver *drv) { netdev.drv = NULL; } int net_init(void) { static uint8_t initialized; if (initialized) return -EALREADY; initialized = 1; #if UIP_STATISTICS == 1 memset(&uip_stat, 0, sizeof(uip_stat)); #endif /* UIP_STATISTICS == 1 */ net_context_init(); ip_buf_init(); l2_buf_init(); init_tx_queue(); init_rx_queue(); init_timer_fiber(); #if defined(CONFIG_NETWORKING_WITH_15_4) net_driver_15_4_init(); #endif net_driver_slip_init(); net_driver_ethernet_init(); return network_initialization(); }