Goto sanos source index

//
// arp.c
//
// Address Resolution Protocol (ARP)
//
// Copyright (C) 2002 Michael Ringgaard. All rights reserved.
// Portions Copyright (C) 2001, Swedish Institute of Computer Science.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 
// 1. Redistributions of source code must retain the above copyright 
//    notice, this list of conditions and the following disclaimer.  
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.  
// 3. Neither the name of the project nor the names of its contributors
//    may be used to endorse or promote products derived from this software
//    without specific prior written permission. 
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 
// SUCH DAMAGE.
// 

#include <net/net.h>

#define HWTYPE_ETHERNET    1

#define ARP_REQUEST        1
#define ARP_REPLY          2

#define ARP_MAXAGE         120         // 120 * 10 seconds = 20 minutes
#define ARP_TIMER_INTERVAL 10000       // The ARP cache is checked every 10 seconds

#define MAX_XMIT_DELAY     1000        // Maximum delay for packets in millisecs     

#pragma pack(push, 1)

struct arp_hdr {
  struct eth_hdr ethhdr;           // Ethernet header
  unsigned short hwtype;           // Hardware type
  unsigned short proto;            // Protocol type
  unsigned short _hwlen_protolen;  // Protocol address length
  unsigned short opcode;           // Opcode
  struct eth_addr shwaddr;         // Source hardware address
  struct ip_addr sipaddr;          // Source protocol address
  struct eth_addr dhwaddr;         // Target hardware address
  struct ip_addr dipaddr;          // Target protocol address
};

struct ethip_hdr {
  struct eth_hdr eth;
  struct ip_hdr ip;
};

#pragma pack(pop)

#define ARPH_HWLEN(hdr) (NTOHS((hdr)->_hwlen_protolen) >> 8)
#define ARPH_PROTOLEN(hdr) (NTOHS((hdr)->_hwlen_protolen) & 0xFF)

#define ARPH_HWLEN_SET(hdr, len) (hdr)->_hwlen_protolen = HTONS(ARPH_PROTOLEN(hdr) | ((len) << 8))
#define ARPH_PROTOLEN_SET(hdr, len) (hdr)->_hwlen_protolen = HTONS((len) | (ARPH_HWLEN(hdr) << 8))

struct arp_entry {
  struct ip_addr ipaddr;
  struct eth_addr ethaddr;
  int ctime;
};

struct xmit_queue_entry {
  struct netif *netif;
  struct pbuf *p;
  struct ip_addr ipaddr;
  unsigned int expires;
};

static struct arp_entry arp_table[ARP_TABLE_SIZE];
static struct xmit_queue_entry xmit_queue_table[ARP_XMIT_QUEUE_SIZE];

struct timer arp_timer;
int arp_ctime;

static int arp_proc(struct proc_file *pf, void *arg) {
  int i;

  for (i = 0; i < ARP_TABLE_SIZE; ++i) {
    if (!ip_addr_isany(&arp_table[i].ipaddr)) {
      pprintf(pf, "%la %a\n", &arp_table[i].ethaddr, &arp_table[i].ipaddr);
    }
  }

  return 0;
}

static void arp_tmr(void *arg) {
  int i;
  
  arp_ctime++;
  for (i = 0; i < ARP_TABLE_SIZE; ++i) {
    if (!ip_addr_isany(&arp_table[i].ipaddr) && arp_ctime - arp_table[i].ctime >= ARP_MAXAGE) {
      //kprintf("arp: expired entry %d\n", i);
      ip_addr_set(&arp_table[i].ipaddr, IP_ADDR_ANY);
    }
  }
  
  for (i = 0; i < ARP_XMIT_QUEUE_SIZE; i++) {
    struct xmit_queue_entry *entry = xmit_queue_table + i;
    if (entry->p && time_before(entry->expires, ticks)) {
      //kprintf("arp: xmit queue entry %d expired\n", i);
      pbuf_free(entry->p);
      entry->p = NULL;
      stats.link.drop++;
    }
  }

  mod_timer(&arp_timer, ticks + ARP_TIMER_INTERVAL / MSECS_PER_TICK);
}

void arp_init() {
  int i;
  
  for (i = 0; i < ARP_TABLE_SIZE; ++i) ip_addr_set(&arp_table[i].ipaddr, IP_ADDR_ANY);
  memset(xmit_queue_table, 0, sizeof(xmit_queue_table));
  init_timer(&arp_timer, arp_tmr, NULL);
  mod_timer(&arp_timer, ticks + ARP_TIMER_INTERVAL / MSECS_PER_TICK);
  register_proc_inode("arp", arp_proc, NULL);
}

static void add_arp_entry(struct ip_addr *ipaddr, struct eth_addr *ethaddr) {
  int i, j, k;
  int maxtime;
  int err;
  
  //kprintf("arp: add %la -> %a\n", ethaddr, ipaddr);

  // Walk through the ARP mapping table and try to find an entry to
  // update. If none is found, the IP -> MAC address mapping is
  // inserted in the ARP table.
  for (i = 0; i < ARP_TABLE_SIZE; i++) {
    // Only check those entries that are actually in use.
    if (!ip_addr_isany(&arp_table[i].ipaddr)) {
      // Check if the source IP address of the incoming packet matches
      // the IP address in this ARP table entry.
      if (ip_addr_cmp(ipaddr, &arp_table[i].ipaddr)) {
        // An old entry found, update this and return.
        for (k = 0; k < 6; ++k) arp_table[i].ethaddr.addr[k] = ethaddr->addr[k];
        arp_table[i].ctime = arp_ctime;
        return;
      }
    }
  }

  // If we get here, no existing ARP table entry was found, so we create one.
  // First, we try to find an unused entry in the ARP table.
  for (i = 0; i < ARP_TABLE_SIZE; i++) {
    if (ip_addr_isany(&arp_table[i].ipaddr)) break;
  }

  // If no unused entry is found, we try to find the oldest entry and throw it away
  if (i == ARP_TABLE_SIZE) {
    maxtime = 0;
    j = 0;
    for (i = 0; i < ARP_TABLE_SIZE; ++i) {
      if (arp_ctime - arp_table[i].ctime > maxtime) {
        maxtime = arp_ctime - arp_table[i].ctime;
        j = i;
      }
    }
    i = j;
  }

  // Now, i is the ARP table entry which we will fill with the new information.
  ip_addr_set(&arp_table[i].ipaddr, ipaddr);
  for (k = 0; k < 6; k++) arp_table[i].ethaddr.addr[k] = ethaddr->addr[k];
  arp_table[i].ctime = arp_ctime;

  // Check for delayed transmissions
  for (i = 0; i < ARP_XMIT_QUEUE_SIZE; i++) {
    struct xmit_queue_entry *entry = xmit_queue_table + i;

    if (entry->p && ip_addr_cmp(&entry->ipaddr, ipaddr)) {
      struct pbuf *p = entry->p;
      struct eth_hdr *ethhdr = p->payload;

      entry->p = NULL;

      for (i = 0; i < 6; i++) {
        ethhdr->dest.addr[i] = ethaddr->addr[i];
        ethhdr->src.addr[i] = entry->netif->hwaddr.addr[i];
      }
      ethhdr->type = htons(ETHTYPE_IP);

      err = dev_transmit((dev_t) entry->netif->state, p);
      if (err < 0) {
        kprintf(KERN_ERR "arp: error %d in delayed transmit\n", err);
        pbuf_free(p);
      }
    }
  }
}

void arp_ip_input(struct netif *netif, struct pbuf *p) {
  struct ethip_hdr *hdr;
  
  hdr = p->payload;
  
  // Only insert/update an entry if the source IP address of the
  // incoming IP packet comes from a host on the local network.
  if (!ip_addr_maskcmp(&hdr->ip.src, &netif->ipaddr, &netif->netmask)) return;

  add_arp_entry(&hdr->ip.src, &hdr->eth.src);
}

struct pbuf *arp_arp_input(struct netif *netif, struct eth_addr *ethaddr, struct pbuf *p) {
  struct arp_hdr *hdr;
  int i;
  
  if (p->tot_len < sizeof(struct arp_hdr)) {
    pbuf_free(p);
    return NULL;
  }

  hdr = p->payload;
  
  switch (htons(hdr->opcode)) {
    case ARP_REQUEST:
      // ARP request. If it asked for our address, we send out a reply
      if (ip_addr_cmp(&hdr->dipaddr, &netif->ipaddr)) {
        hdr->opcode = htons(ARP_REPLY);

        ip_addr_set(&hdr->dipaddr, &hdr->sipaddr);
        ip_addr_set(&hdr->sipaddr, &netif->ipaddr);

        for (i = 0; i < 6; i++) {
          hdr->dhwaddr.addr[i] = hdr->shwaddr.addr[i];
          hdr->shwaddr.addr[i] = ethaddr->addr[i];
          hdr->ethhdr.dest.addr[i] = hdr->dhwaddr.addr[i];
          hdr->ethhdr.src.addr[i] = ethaddr->addr[i];
        }

        hdr->hwtype = htons(HWTYPE_ETHERNET);
        ARPH_HWLEN_SET(hdr, 6);
      
        hdr->proto = htons(ETHTYPE_IP);
        ARPH_PROTOLEN_SET(hdr, sizeof(struct ip_addr));      
      
        hdr->ethhdr.type = htons(ETHTYPE_ARP);      
        return p;
      }
      break;

    case ARP_REPLY:
      // ARP reply. We insert or update the ARP table.
      if (ip_addr_cmp(&hdr->dipaddr, &netif->ipaddr)) {
        add_arp_entry(&hdr->sipaddr, &hdr->shwaddr);
        dhcp_arp_reply(&hdr->sipaddr);
      }
      break;

    default:
      break;
  }

  pbuf_free(p);
  return NULL;
}

struct eth_addr *arp_lookup(struct ip_addr *ipaddr) {
  int i;
  
  for (i = 0; i < ARP_TABLE_SIZE; ++i) {
    if (ip_addr_cmp(ipaddr, &arp_table[i].ipaddr)) return &arp_table[i].ethaddr;
  }
  return NULL;  
}

struct pbuf *arp_query(struct netif *netif, struct eth_addr *ethaddr, struct ip_addr *ipaddr) {
  struct arp_hdr *hdr;
  struct pbuf *p;
  int i;

  p = pbuf_alloc(PBUF_LINK, sizeof(struct arp_hdr), PBUF_RW);
  if (p == NULL) return NULL;

  hdr = p->payload;
  hdr->opcode = htons(ARP_REQUEST);

  for (i = 0; i < 6; ++i) {
    hdr->dhwaddr.addr[i] = 0x00;
    hdr->shwaddr.addr[i] = ethaddr->addr[i];
  }
  
  ip_addr_set(&hdr->dipaddr, ipaddr);
  ip_addr_set(&hdr->sipaddr, &netif->ipaddr);

  hdr->hwtype = htons(HWTYPE_ETHERNET);
  ARPH_HWLEN_SET(hdr, 6);

  hdr->proto = htons(ETHTYPE_IP);
  ARPH_PROTOLEN_SET(hdr, sizeof(struct ip_addr));

  for (i = 0; i < 6; ++i) {
    hdr->ethhdr.dest.addr[i] = 0xFF;
    hdr->ethhdr.src.addr[i] = ethaddr->addr[i];
  }
  
  hdr->ethhdr.type = htons(ETHTYPE_ARP);      
  return p;
}

int arp_queue(struct netif *netif, struct pbuf *p, struct ip_addr *ipaddr) {
  int i;
  struct xmit_queue_entry *entry = NULL;

  // Find empty entry
  for (i = 0; i < ARP_XMIT_QUEUE_SIZE; i++) {
    if (xmit_queue_table[i].p == NULL) {
      entry = &xmit_queue_table[i];
      break;
    }
  }

  // If no entry entry found, try to find an expired entry
  if (entry == NULL) {
    for (i = 0; i < ARP_XMIT_QUEUE_SIZE; i++) {
      if (time_before(xmit_queue_table[i].expires, ticks)) {
        entry = &xmit_queue_table[i];
        break;
      }
    }
  }

  // If there are no room in the xmit queue, we have to drop the packet
  if (!entry) {
    stats.link.drop++;
    return -ENOMEM;
  }

  // Expire entry if it is not empty
  if (entry->p) {
    pbuf_free(entry->p);
    stats.link.drop++;
  }

  // Fill xmit queue entry
  entry->netif = netif;
  entry->p = p;
  ip_addr_set(&entry->ipaddr, ipaddr);
  entry->expires = ticks + MAX_XMIT_DELAY / MSECS_PER_TICK;
  
  return 0;
}