Free (GPLv2) TCP/IP stack developed by TASS Belgium

Fork of PicoTCP by Daniele Lacamera

stack/pico_arp.c

Committer:
daniele
Date:
2013-08-03
Revision:
51:18637a3d071f
Parent:
3:b4047e8a0123

File content as of revision 51:18637a3d071f:

/*********************************************************************
PicoTCP. Copyright (c) 2012 TASS Belgium NV. Some rights reserved.
See LICENSE and COPYING for usage.

.

Authors: Daniele Lacamera
*********************************************************************/


#include "pico_config.h"
#include "pico_arp.h"
#include "pico_tree.h"
#include "pico_ipv4.h"
#include "pico_device.h"
#include "pico_stack.h"

const uint8_t PICO_ETHADDR_ALL[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
#define PICO_ARP_TIMEOUT 600000
#define PICO_ARP_RETRY 300

#ifdef DEBUG_ARP
    #define arp_dbg dbg
#else
    #define arp_dbg(...) do{}while(0)
#endif

static struct pico_queue pending;
static int pending_timer_on = 0;

void check_pending(unsigned long now, void *_unused)
{
  struct pico_frame *f = pico_dequeue(&pending);
  if (!f) {
    pending_timer_on = 0;
    return;
  }
  if(pico_ethernet_send(f) > 0)
    pico_frame_discard(f);
  pico_timer_add(PICO_ARP_RETRY, &check_pending, NULL);
}


struct
__attribute__ ((__packed__)) 
pico_arp_hdr
{
  uint16_t htype;
  uint16_t ptype;
  uint8_t hsize;
  uint8_t psize;
  uint16_t opcode;
  uint8_t s_mac[PICO_SIZE_ETH];
  struct pico_ip4 src;
  uint8_t d_mac[PICO_SIZE_ETH];
  struct pico_ip4 dst;
};


#define PICO_SIZE_ARPHDR ((sizeof(struct pico_arp_hdr)))

/* Arp Entries for the tables. */
struct pico_arp {
/* CAREFUL MAN! ARP entry MUST begin with a pico_eth structure, 
 * due to in-place casting!!! */
  struct pico_eth eth;
  struct pico_ip4 ipv4;
  int    arp_status;
  uint32_t timestamp;
  struct pico_device *dev;
};



/*****************/
/**  ARP TREE **/
/*****************/

/* Routing destination */

static int arp_compare(void * ka, void * kb)
{
    struct pico_arp *a = ka, *b = kb;
  if (a->ipv4.addr < b->ipv4.addr)
    return -1;
  else if (a->ipv4.addr > b->ipv4.addr)
    return 1;
  return 0;
}

PICO_TREE_DECLARE(arp_tree, arp_compare);

/*********************/
/**  END ARP TREE **/
/*********************/

struct pico_eth *pico_arp_lookup(struct pico_ip4 *dst)
{
  struct pico_arp search, *found;
  search.ipv4.addr = dst->addr;
  found = pico_tree_findKey(&arp_tree,&search);
  if (found && (found->arp_status != PICO_ARP_STATUS_STALE))
    return &found->eth;
  return NULL;
}

struct pico_ip4 *pico_arp_reverse_lookup(struct pico_eth *dst)
{
  struct pico_arp* search;
  struct pico_tree_node * index;
  pico_tree_foreach(index,&arp_tree){
      search = index->keyValue;
    if(memcmp(&(search->eth.addr), &dst->addr, 6) == 0)
      return &search->ipv4;
  }
  return NULL;
}

struct pico_eth *pico_arp_get(struct pico_frame *f) {
  struct pico_eth *a4;
  struct pico_ip4 gateway;
  struct pico_ipv4_hdr *hdr = (struct pico_ipv4_hdr *) f->net_hdr;
  struct pico_ipv4_link *l;

  l = pico_ipv4_link_get(&hdr->dst);
  if(l){
    //address belongs to ourself
    return &l->dev->eth->mac;
  }

  gateway = pico_ipv4_route_get_gateway(&hdr->dst);
  /* check if dst is local (gateway = 0), or if to use gateway */
  if (gateway.addr != 0)
    a4 = pico_arp_lookup(&gateway);          /* check if gateway ip mac in cache */
  else
    a4 = pico_arp_lookup(&hdr->dst);         /* check if local ip mac in cache */
  if (!a4) {
     if (++f->failure_count < 4) {
       dbg ("================= ARP REQUIRED: %d =============\n\n", f->failure_count);
       /* check if dst is local (gateway = 0), or if to use gateway */
       if (gateway.addr != 0)
         pico_arp_query(f->dev, &gateway);  /* arp to gateway */
       else
         pico_arp_query(f->dev, &hdr->dst); /* arp to dst */

       pico_enqueue(&pending, f);
       if (!pending_timer_on) {
        pending_timer_on++;
        pico_timer_add(PICO_ARP_RETRY, &check_pending, NULL);
       }
     } else {
      dbg("ARP: Destination Unreachable\n");
      pico_notify_dest_unreachable(f);
      pico_frame_discard(f);
    }
  }
  return a4;
}

#ifdef DEBUG_ARP
void dbg_arp(void)
{
  struct pico_arp *a;
  struct pico_tree_node * index;

  pico_tree_foreach(index,&arp_tree) {
      a = index->keyValue;
    arp_dbg("ARP to  %08x, mac: %02x:%02x:%02x:%02x:%02x:%02x\n", a->ipv4.addr,a->eth.addr[0],a->eth.addr[1],a->eth.addr[2],a->eth.addr[3],a->eth.addr[4],a->eth.addr[5] );
  }
}
#endif

void arp_expire(unsigned long now, void *_stale)
{
  struct pico_arp *stale = (struct pico_arp *) _stale;
  stale->arp_status = PICO_ARP_STATUS_STALE;
  arp_dbg("ARP: Setting arp_status to STALE\n");
  pico_arp_query(stale->dev, &stale->ipv4);

}

void pico_arp_add_entry(struct pico_arp *entry)
{
    entry->arp_status = PICO_ARP_STATUS_REACHABLE;
    entry->timestamp  = PICO_TIME();

    pico_tree_insert(&arp_tree,entry);
    arp_dbg("ARP ## reachable.\n");
    pico_timer_add(PICO_ARP_TIMEOUT, arp_expire, entry);
}

int pico_arp_create_entry(uint8_t* hwaddr, struct pico_ip4 ipv4, struct pico_device* dev)
{
    struct pico_arp* arp = pico_zalloc(sizeof(struct pico_arp));
    if(!arp){
        pico_err = PICO_ERR_ENOMEM;
        return -1;
    }
    memcpy(arp->eth.addr, hwaddr, 6);
    arp->ipv4.addr = ipv4.addr;
    arp->dev = dev;

    pico_arp_add_entry(arp);

    return 0;
}

int pico_arp_receive(struct pico_frame *f)
{
  struct pico_arp_hdr *hdr;
  struct pico_arp search, *found, *new = NULL;
  int ret = -1;
  hdr = (struct pico_arp_hdr *) f->net_hdr;

  if (!hdr)
    goto end;


  /* Populate a new arp entry */
  search.ipv4.addr = hdr->src.addr;
  memcpy(search.eth.addr, hdr->s_mac, PICO_SIZE_ETH);

  /* Search for already existing entry */

  found = pico_tree_findKey(&arp_tree,&search);
  if (!found) {
    new = pico_zalloc(sizeof(struct pico_arp));
    if (!new)
      goto end;
    new->ipv4.addr = hdr->src.addr;
  }
  else if (found->arp_status == PICO_ARP_STATUS_STALE) {
    /* Replace if stale */
    new = found;

    pico_tree_delete(&arp_tree,new);
  }

  ret = 0;

  if (new) {
    memcpy(new->eth.addr, hdr->s_mac, PICO_SIZE_ETH);
    new->dev = f->dev;
    pico_arp_add_entry(new);
  }

  if (hdr->opcode == PICO_ARP_REQUEST) {
    struct pico_ip4 me;
    struct pico_eth_hdr *eh = (struct pico_eth_hdr *)f->datalink_hdr;
    struct pico_device *link_dev;
    me.addr = hdr->dst.addr;

    link_dev = pico_ipv4_link_find(&me);
    if (link_dev != f->dev)
      goto end;

    hdr->opcode = PICO_ARP_REPLY;
    memcpy(hdr->d_mac, hdr->s_mac, PICO_SIZE_ETH);
    memcpy(hdr->s_mac, f->dev->eth->mac.addr, PICO_SIZE_ETH);
    hdr->dst.addr = hdr->src.addr;
    hdr->src.addr = me.addr;

    /* Prepare eth header for arp reply */
    memcpy(eh->daddr, eh->saddr, PICO_SIZE_ETH);
    memcpy(eh->saddr, f->dev->eth->mac.addr, PICO_SIZE_ETH);
    f->start = f->datalink_hdr;
    f->len = PICO_SIZE_ETHHDR + PICO_SIZE_ARPHDR;
    f->dev->send(f->dev, f->start, f->len);
  }

#ifdef DEBUG_ARG
  dbg_arp();
#endif

end:
  pico_frame_discard(f);
  return ret;
}

int pico_arp_query(struct pico_device *dev, struct pico_ip4 *dst)
{
  struct pico_frame *q = pico_frame_alloc(PICO_SIZE_ETHHDR + PICO_SIZE_ARPHDR);
  struct pico_eth_hdr *eh;
  struct pico_arp_hdr *ah;
  struct pico_ip4 *src;
  int ret;

  src = pico_ipv4_source_find(dst);
  if (!src)
    return -1;

  arp_dbg("QUERY: %08x\n", dst->addr);

  if (!q)
    return -1;
  eh = (struct pico_eth_hdr *)q->start;
  ah = (struct pico_arp_hdr *) (q->start + PICO_SIZE_ETHHDR);

  /* Fill eth header */
  memcpy(eh->saddr, dev->eth->mac.addr, PICO_SIZE_ETH);
  memcpy(eh->daddr, PICO_ETHADDR_ALL, PICO_SIZE_ETH);
  eh->proto = PICO_IDETH_ARP;

  /* Fill arp header */
  ah->htype  = PICO_ARP_HTYPE_ETH;
  ah->ptype  = PICO_IDETH_IPV4;
  ah->hsize  = PICO_SIZE_ETH;
  ah->psize  = PICO_SIZE_IP4;
  ah->opcode = PICO_ARP_REQUEST;
  memcpy(ah->s_mac, dev->eth->mac.addr, PICO_SIZE_ETH);
  ah->src.addr = src->addr;
  ah->dst.addr = dst->addr;
  arp_dbg("Sending arp query.\n");
  ret = dev->send(dev, q->start, q->len);
  pico_frame_discard(q);
  return ret;
}