483 lines
13 KiB
C
483 lines
13 KiB
C
/*********************************************************************
|
|
PicoTCP. Copyright (c) 2012-2017 Altran Intelligent Systems. Some rights reserved.
|
|
See COPYING, LICENSE.GPLv2 and LICENSE.GPLv3 for usage.
|
|
|
|
.
|
|
|
|
Authors: Daniele Lacamera
|
|
*********************************************************************/
|
|
|
|
#include "pico_config.h"
|
|
#include "pico_device.h"
|
|
#include "pico_stack.h"
|
|
#include "pico_protocol.h"
|
|
#include "pico_tree.h"
|
|
#include "pico_ipv6.h"
|
|
#include "pico_ipv4.h"
|
|
#include "pico_icmp6.h"
|
|
#include "pico_eth.h"
|
|
#include "pico_802154.h"
|
|
#include "pico_6lowpan.h"
|
|
#include "pico_6lowpan_ll.h"
|
|
#include "pico_addressing.h"
|
|
#define PICO_DEVICE_DEFAULT_MTU (1500)
|
|
|
|
struct pico_devices_rr_info {
|
|
struct pico_tree_node *node_in, *node_out;
|
|
};
|
|
|
|
static struct pico_devices_rr_info Devices_rr_info = {
|
|
NULL, NULL
|
|
};
|
|
|
|
static int pico_dev_cmp(void *ka, void *kb)
|
|
{
|
|
struct pico_device *a = ka, *b = kb;
|
|
if (a->hash < b->hash)
|
|
return -1;
|
|
|
|
|
|
if (a->hash > b->hash)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
PICO_TREE_DECLARE(Device_tree, pico_dev_cmp);
|
|
|
|
#ifdef PICO_SUPPORT_6LOWPAN
|
|
static struct pico_ipv6_link * pico_6lowpan_link_add(struct pico_device *dev, const struct pico_ip6 *prefix)
|
|
{
|
|
struct pico_ip6 netmask64 = {{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
|
struct pico_6lowpan_info *info = (struct pico_6lowpan_info *)dev->eth;
|
|
struct pico_ipv6_link *link = NULL; /* Make sure to return NULL */
|
|
struct pico_ip6 newaddr;
|
|
|
|
memcpy(newaddr.addr, prefix->addr, PICO_SIZE_IP6);
|
|
memcpy(newaddr.addr + 8, info->addr_ext.addr, SIZE_6LOWPAN_EXT);
|
|
newaddr.addr[8] = newaddr.addr[8] ^ 0x02; /* Toggle U/L bit */
|
|
|
|
/* RFC6775: No Duplicate Address Detection (DAD) is performed if
|
|
* EUI-64-based IPv6 addresses are used (as these addresses are assumed
|
|
* to be globally unique). */
|
|
if ((link = pico_ipv6_link_add_no_dad(dev, newaddr, netmask64))) {
|
|
if (pico_ipv6_is_linklocal(newaddr.addr))
|
|
pico_6lp_nd_start_soliciting(link, NULL);
|
|
else
|
|
pico_6lp_nd_register(link);
|
|
}
|
|
|
|
return link;
|
|
}
|
|
|
|
static int pico_6lowpan_store_info(struct pico_device *dev, const uint8_t *mac)
|
|
{
|
|
if ((dev->eth = PICO_ZALLOC(sizeof(struct pico_6lowpan_info)))) {
|
|
memcpy(dev->eth, mac, sizeof(struct pico_6lowpan_info));
|
|
return 0;
|
|
} else {
|
|
pico_err = PICO_ERR_ENOMEM;
|
|
return -1;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef PICO_SUPPORT_IPV6
|
|
static void device_init_ipv6_final(struct pico_device *dev, struct pico_ip6 *linklocal)
|
|
{
|
|
dev->hostvars.basetime = PICO_ND_REACHABLE_TIME;
|
|
/* RFC 4861 $6.3.2 value between 0.5 and 1.5 times basetime */
|
|
dev->hostvars.reachabletime = ((5 + (pico_rand() % 10)) * PICO_ND_REACHABLE_TIME) / 10;
|
|
dev->hostvars.retranstime = PICO_ND_RETRANS_TIMER;
|
|
pico_icmp6_router_solicitation(dev, linklocal, NULL);
|
|
dev->hostvars.hoplimit = PICO_IPV6_DEFAULT_HOP;
|
|
}
|
|
|
|
struct pico_ipv6_link *pico_ipv6_link_add_local(struct pico_device *dev, const struct pico_ip6 *prefix)
|
|
{
|
|
struct pico_ip6 netmask64 = {{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
|
struct pico_ipv6_link *link = NULL; /* Make sure to return NULL */
|
|
struct pico_ip6 newaddr;
|
|
|
|
if (0) {}
|
|
#ifdef PICO_SUPPORT_6LOWPAN
|
|
else if (PICO_DEV_IS_6LOWPAN(dev)) {
|
|
link = pico_6lowpan_link_add(dev, prefix);
|
|
}
|
|
#endif
|
|
else {
|
|
memcpy(newaddr.addr, prefix->addr, PICO_SIZE_IP6);
|
|
/* modified EUI-64 + invert universal/local bit */
|
|
newaddr.addr[8] = (dev->eth->mac.addr[0] ^ 0x02);
|
|
newaddr.addr[9] = dev->eth->mac.addr[1];
|
|
newaddr.addr[10] = dev->eth->mac.addr[2];
|
|
newaddr.addr[11] = 0xff;
|
|
newaddr.addr[12] = 0xfe;
|
|
newaddr.addr[13] = dev->eth->mac.addr[3];
|
|
newaddr.addr[14] = dev->eth->mac.addr[4];
|
|
newaddr.addr[15] = dev->eth->mac.addr[5];
|
|
if ((link = pico_ipv6_link_add(dev, newaddr, netmask64))) {
|
|
device_init_ipv6_final(dev, &newaddr);
|
|
}
|
|
}
|
|
|
|
return link;
|
|
}
|
|
#endif
|
|
static int device_init_mac(struct pico_device *dev, const uint8_t *mac)
|
|
{
|
|
#ifdef PICO_SUPPORT_IPV6
|
|
struct pico_ip6 linklocal = {{0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa, 0xaa, 0xaa, 0xff, 0xfe, 0xaa, 0xaa, 0xaa}};
|
|
#endif
|
|
|
|
if (0) {}
|
|
#ifdef PICO_SUPPORT_6LOWPAN
|
|
else if (PICO_DEV_IS_6LOWPAN(dev)) {
|
|
if (pico_6lowpan_store_info(dev, mac))
|
|
return -1;
|
|
}
|
|
#endif
|
|
else {
|
|
if ((dev->eth = PICO_ZALLOC(sizeof(struct pico_ethdev)))) {
|
|
memcpy(dev->eth->mac.addr, mac, PICO_SIZE_ETH);
|
|
} else {
|
|
pico_err = PICO_ERR_ENOMEM;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
#ifdef PICO_SUPPORT_IPV6
|
|
if (pico_ipv6_link_add_local(dev, &linklocal) == NULL) {
|
|
PICO_FREE(dev->q_in);
|
|
PICO_FREE(dev->q_out);
|
|
PICO_FREE(dev->eth);
|
|
return -1;
|
|
}
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
int pico_device_ipv6_random_ll(struct pico_device *dev)
|
|
{
|
|
#ifdef PICO_SUPPORT_IPV6
|
|
struct pico_ip6 linklocal = {{0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa, 0xaa, 0xaa, 0xff, 0xfe, 0xaa, 0xaa, 0xaa}};
|
|
struct pico_ip6 netmask6 = {{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
|
uint32_t len = (uint32_t)strlen(dev->name);
|
|
if (strcmp(dev->name, "loop")) {
|
|
do {
|
|
/* privacy extension + unset universal/local and individual/group bit */
|
|
len = pico_rand();
|
|
linklocal.addr[8] = (uint8_t)((len & 0xffu) & (uint8_t)(~0x03));
|
|
linklocal.addr[9] = (uint8_t)(len >> 8);
|
|
linklocal.addr[10] = (uint8_t)(len >> 16);
|
|
linklocal.addr[11] = (uint8_t)(len >> 24);
|
|
len = pico_rand();
|
|
linklocal.addr[12] = (uint8_t)len;
|
|
linklocal.addr[13] = (uint8_t)(len >> 8);
|
|
linklocal.addr[14] = (uint8_t)(len >> 16);
|
|
linklocal.addr[15] = (uint8_t)(len >> 24);
|
|
pico_rand_feed(dev->hash);
|
|
} while (pico_ipv6_link_get(&linklocal));
|
|
|
|
if (pico_ipv6_link_add(dev, linklocal, netmask6) == NULL) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
static int device_init_nomac(struct pico_device *dev)
|
|
{
|
|
if (pico_device_ipv6_random_ll(dev) < 0) {
|
|
PICO_FREE(dev->q_in);
|
|
PICO_FREE(dev->q_out);
|
|
return -1;
|
|
}
|
|
|
|
dev->eth = NULL;
|
|
return 0;
|
|
}
|
|
|
|
#define DEBUG_IPV6(ip) { \
|
|
char ipstr[40] = { 0 }; \
|
|
pico_ipv6_to_string(ipstr, (ip).addr); \
|
|
dbg("IPv6 (%s)\n", ipstr); \
|
|
}
|
|
|
|
int pico_device_init(struct pico_device *dev, const char *name, const uint8_t *mac)
|
|
{
|
|
uint32_t len = (uint32_t)strlen(name);
|
|
int ret = 0;
|
|
|
|
if(len > MAX_DEVICE_NAME)
|
|
len = MAX_DEVICE_NAME;
|
|
|
|
memcpy(dev->name, name, len);
|
|
dev->hash = pico_hash(dev->name, len);
|
|
|
|
Devices_rr_info.node_in = NULL;
|
|
Devices_rr_info.node_out = NULL;
|
|
dev->q_in = PICO_ZALLOC(sizeof(struct pico_queue));
|
|
if (!dev->q_in)
|
|
return -1;
|
|
|
|
dev->q_out = PICO_ZALLOC(sizeof(struct pico_queue));
|
|
if (!dev->q_out) {
|
|
PICO_FREE(dev->q_in);
|
|
return -1;
|
|
}
|
|
|
|
if (pico_tree_insert(&Device_tree, dev)) {
|
|
PICO_FREE(dev->q_in);
|
|
PICO_FREE(dev->q_out);
|
|
return -1;
|
|
}
|
|
if (!dev->mtu)
|
|
dev->mtu = PICO_DEVICE_DEFAULT_MTU;
|
|
|
|
#ifdef PICO_SUPPORT_6LOWPAN
|
|
if (PICO_DEV_IS_6LOWPAN(dev) && LL_MODE_ETHERNET == dev->mode)
|
|
return -1;
|
|
#endif
|
|
|
|
if (mac) {
|
|
ret = device_init_mac(dev, mac);
|
|
} else {
|
|
if (!dev->mode) {
|
|
ret = device_init_nomac(dev);
|
|
}
|
|
#ifdef PICO_SUPPORT_6LOWPAN
|
|
else {
|
|
/* RFC6775: Link Local to be formed based on EUI-64 as per RFC6775 */
|
|
dbg("Link local address to be formed based on EUI-64\n");
|
|
return -1;
|
|
}
|
|
#endif
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static void pico_queue_destroy(struct pico_queue *q)
|
|
{
|
|
if (q) {
|
|
pico_queue_empty(q);
|
|
PICO_FREE(q);
|
|
}
|
|
}
|
|
|
|
void pico_device_destroy(struct pico_device *dev)
|
|
{
|
|
|
|
pico_queue_destroy(dev->q_in);
|
|
pico_queue_destroy(dev->q_out);
|
|
|
|
if (!dev->mode && dev->eth)
|
|
PICO_FREE(dev->eth);
|
|
|
|
#ifdef PICO_SUPPORT_IPV4
|
|
pico_ipv4_cleanup_links(dev);
|
|
#endif
|
|
#ifdef PICO_SUPPORT_IPV6
|
|
pico_ipv6_cleanup_links(dev);
|
|
#endif
|
|
pico_tree_delete(&Device_tree, dev);
|
|
|
|
if (dev->destroy)
|
|
dev->destroy(dev);
|
|
|
|
Devices_rr_info.node_in = NULL;
|
|
Devices_rr_info.node_out = NULL;
|
|
PICO_FREE(dev);
|
|
}
|
|
|
|
static int check_dev_serve_interrupt(struct pico_device *dev, int loop_score)
|
|
{
|
|
if ((dev->__serving_interrupt) && (dev->dsr)) {
|
|
/* call dsr routine */
|
|
loop_score = dev->dsr(dev, loop_score);
|
|
}
|
|
|
|
return loop_score;
|
|
}
|
|
|
|
static int check_dev_serve_polling(struct pico_device *dev, int loop_score)
|
|
{
|
|
if (dev->poll) {
|
|
loop_score = dev->poll(dev, loop_score);
|
|
}
|
|
|
|
return loop_score;
|
|
}
|
|
|
|
static int devloop_in(struct pico_device *dev, int loop_score)
|
|
{
|
|
struct pico_frame *f;
|
|
while(loop_score > 0) {
|
|
if (dev->q_in->frames == 0)
|
|
break;
|
|
|
|
/* Receive */
|
|
f = pico_dequeue(dev->q_in);
|
|
if (f) {
|
|
pico_datalink_receive(f);
|
|
loop_score--;
|
|
}
|
|
}
|
|
return loop_score;
|
|
}
|
|
|
|
static int devloop_sendto_dev(struct pico_device *dev, struct pico_frame *f)
|
|
{
|
|
#ifdef PICO_SUPPORT_6LOWPAN
|
|
if (PICO_DEV_IS_6LOWPAN(dev)) {
|
|
return (pico_6lowpan_ll_sendto_dev(dev, f) <= 0);
|
|
}
|
|
#endif
|
|
return (dev->send(dev, f->start, (int)f->len) <= 0);
|
|
}
|
|
|
|
static int devloop_out(struct pico_device *dev, int loop_score)
|
|
{
|
|
struct pico_frame *f;
|
|
while(loop_score > 0) {
|
|
if (dev->q_out->frames == 0)
|
|
break;
|
|
|
|
/* Device dequeue + send */
|
|
f = pico_queue_peek(dev->q_out);
|
|
if (!f)
|
|
break;
|
|
|
|
if (devloop_sendto_dev(dev, f) == 0) { /* success. */
|
|
f = pico_dequeue(dev->q_out);
|
|
pico_frame_discard(f); /* SINGLE POINT OF DISCARD for OUTGOING FRAMES */
|
|
loop_score--;
|
|
} else
|
|
break; /* Don't discard */
|
|
|
|
}
|
|
|
|
return loop_score;
|
|
}
|
|
|
|
static int devloop(struct pico_device *dev, int loop_score, int direction)
|
|
{
|
|
/* If device supports interrupts, read the value of the condition and trigger the dsr */
|
|
loop_score = check_dev_serve_interrupt(dev, loop_score);
|
|
|
|
/* If device supports polling, give control. Loop score is managed internally,
|
|
* remaining loop points are returned. */
|
|
loop_score = check_dev_serve_polling(dev, loop_score);
|
|
|
|
if (direction == PICO_LOOP_DIR_OUT)
|
|
loop_score = devloop_out(dev, loop_score);
|
|
else
|
|
loop_score = devloop_in(dev, loop_score);
|
|
|
|
return loop_score;
|
|
}
|
|
|
|
|
|
static struct pico_tree_node *pico_dev_roundrobin_start(int direction)
|
|
{
|
|
if (Devices_rr_info.node_in == NULL)
|
|
Devices_rr_info.node_in = pico_tree_firstNode(Device_tree.root);
|
|
|
|
if (Devices_rr_info.node_out == NULL)
|
|
Devices_rr_info.node_out = pico_tree_firstNode(Device_tree.root);
|
|
|
|
if (direction == PICO_LOOP_DIR_IN)
|
|
return Devices_rr_info.node_in;
|
|
else
|
|
return Devices_rr_info.node_out;
|
|
}
|
|
|
|
static void pico_dev_roundrobin_end(int direction, struct pico_tree_node *last)
|
|
{
|
|
if (direction == PICO_LOOP_DIR_IN)
|
|
Devices_rr_info.node_in = last;
|
|
else
|
|
Devices_rr_info.node_out = last;
|
|
}
|
|
|
|
#define DEV_LOOP_MIN 16
|
|
|
|
int pico_devices_loop(int loop_score, int direction)
|
|
{
|
|
struct pico_device *start, *next;
|
|
struct pico_tree_node *next_node = pico_dev_roundrobin_start(direction);
|
|
|
|
if (!next_node)
|
|
return loop_score;
|
|
|
|
next = next_node->keyValue;
|
|
start = next;
|
|
|
|
/* round-robin all devices, break if traversed all devices */
|
|
while ((loop_score > DEV_LOOP_MIN) && (next != NULL)) {
|
|
loop_score = devloop(next, loop_score, direction);
|
|
next_node = pico_tree_next(next_node);
|
|
next = next_node->keyValue;
|
|
if (next == NULL)
|
|
{
|
|
next_node = pico_tree_firstNode(Device_tree.root);
|
|
next = next_node->keyValue;
|
|
}
|
|
|
|
if (next == start)
|
|
break;
|
|
}
|
|
pico_dev_roundrobin_end(direction, next_node);
|
|
return loop_score;
|
|
}
|
|
|
|
struct pico_device *pico_get_device(const char*name)
|
|
{
|
|
struct pico_device *dev;
|
|
struct pico_tree_node *index;
|
|
pico_tree_foreach(index, &Device_tree){
|
|
dev = index->keyValue;
|
|
if(strcmp(name, dev->name) == 0)
|
|
return dev;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
int32_t pico_device_broadcast(struct pico_frame *f)
|
|
{
|
|
struct pico_tree_node *index;
|
|
int32_t ret = -1;
|
|
|
|
pico_tree_foreach(index, &Device_tree)
|
|
{
|
|
struct pico_device *dev = index->keyValue;
|
|
if(dev != f->dev)
|
|
{
|
|
struct pico_frame *copy = pico_frame_copy(f);
|
|
|
|
if(!copy)
|
|
break;
|
|
|
|
copy->dev = dev;
|
|
copy->dev->send(copy->dev, copy->start, (int)copy->len);
|
|
pico_frame_discard(copy);
|
|
}
|
|
else
|
|
{
|
|
ret = f->dev->send(f->dev, f->start, (int)f->len);
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
int pico_device_link_state(struct pico_device *dev)
|
|
{
|
|
if (!dev->link_state)
|
|
return 1; /* Not supported, assuming link is always up */
|
|
|
|
return dev->link_state(dev);
|
|
}
|