Goto sanos source index

//
// ip.c
//
// Internet Protocol (IP)
//
// 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>

void ip_debug_print(struct pbuf *p);

//
// ip_init
//
// Initializes the IP layer.
//

void ip_init() {
}

//
// ip_ownaddr
//
// Returns 1 if the IP address is the IP address of one of
// the configured interfaces.
//

int ip_ownaddr(struct ip_addr *addr) {
  struct netif *netif;
  
  for (netif = netif_list; netif != NULL; netif = netif->next) {
    if (!ip_addr_isany(addr) && ip_addr_cmp(addr, &netif->ipaddr)) return 1;
  }

  return 0;
}

//
// ip_route
//
// Finds the appropriate network interface for a given IP address. It
// searches the list of network interfaces linearly. A match is found
// if the masked IP address of the network interface equals the masked
// IP address given to the function. The routine also detects if the
// address is the IP address of one of the interfaces.
//

struct netif *ip_route(struct ip_addr *dest) {
  struct netif *netif;
  
  for (netif = netif_list; netif != NULL; netif = netif->next) {
    if (ip_addr_cmp(dest, &netif->ipaddr)) return netif;
  }

  for (netif = netif_list; netif != NULL; netif = netif->next) {
    if (ip_addr_maskcmp(dest, &netif->ipaddr, &netif->netmask)) {
      //kprintf("ip: route packet to %a to interface %s\n", dest, netif->name);
      return netif;
    }
  }

  if (netif_default) {
    //kprintf("ip: route packet to %a to default interface %s\n", dest, netif_default->name);
    return netif_default;
  }

  return NULL;
}

//
// ip_forward
//
// Forwards an IP packet. It finds an appropriate route for the packet, decrements
// the TTL value of the packet, adjusts the checksum and outputs the packet on the
// appropriate interface.
//

static err_t ip_forward(struct pbuf *p, struct ip_hdr *iphdr, struct netif *inp) {
  struct netif *netif;

  // Don't route broadcasts
  if (ip_addr_isbroadcast(&iphdr->dest, &inp->netmask)) {
    pbuf_free(p);
    return 0;
  }

  // Find route for packet
  if ((netif = ip_route(&iphdr->dest)) == NULL) {
    kprintf("ip_forward: no forwarding route for %a found\n", &iphdr->dest);
    return -EROUTE;
  }

  // Don't forward packets onto the same network interface on which they arrived
  if (netif == inp) {
    //kprintf("ip_forward: not forward packets back on incoming interface. (%a->%a)\n", &iphdr->src, &iphdr->dest);
    return -EROUTE;
  }
  
  // Decrement TTL and send ICMP if ttl == 0
  IPH_TTL_SET(iphdr, IPH_TTL(iphdr) - 1);
  if (IPH_TTL(iphdr) == 0) {
    // Don't send ICMP messages in response to ICMP messages
    if (IPH_PROTO(iphdr) != IP_PROTO_ICMP) icmp_time_exceeded(p, ICMP_TE_TTL);
    pbuf_free(p);
    return 0;
  }
  
  // Incremental update of the IP checksum
  if (IPH_CHKSUM(iphdr) >= htons(0xffff - 0x100)) {
    IPH_CHKSUM_SET(iphdr, IPH_CHKSUM(iphdr) + htons(0x100) + 1);
  } else {
    IPH_CHKSUM_SET(iphdr, (unsigned short) (IPH_CHKSUM(iphdr) + htons(0x100)));
  }
  kprintf("ip_forward: forwarding packet to %a\n", &iphdr->dest);

  stats.ip.fw++;
  stats.ip.xmit++;

  return netif->output(netif, p, &iphdr->dest);
}

//
// ip_input
//
// This function is called by the network interface device driver when an IP packet is
// received. The function does the basic checks of the IP header such as packet size
// being at least larger than the header size etc. If the packet was not destined for
// us, the packet is forwarded (using ip_forward). The IP checksum is always checked.
//
// Finally, the packet is sent to the upper layer protocol input function.
//

err_t ip_input(struct pbuf *p, struct netif *inp) {
  struct ip_hdr *iphdr;
  struct netif *netif;
  int hl;
  int iphdrlen;
  int rc;

  stats.ip.recv++;
  
  //kprintf("receiving IP datagram on %s:\n", inp->name);
  //ip_debug_print(p);

  // Identify the IP header
  iphdr = p->payload;
  if (IPH_V(iphdr) != 4) {
    kprintf("IP packet dropped due to bad version number %d\n", IPH_V(iphdr));
    stats.ip.err++;
    stats.ip.drop++;
    return -EPROTO;
  }
  
  // Check header length
  hl = IPH_HL(iphdr);
  iphdrlen = hl * 4;
  if (iphdrlen > p->len) {
    kprintf("IP packet dropped due to too short packet %d\n", p->len);

    stats.ip.lenerr++;
    stats.ip.drop++;
    return -EPROTO;
  }

#ifdef CHECK_IP_CHECKSUM
  // Verify checksum
  if ((inp->flags & NETIF_IP_RX_CHECKSUM_OFFLOAD) == 0) {
    if (inet_chksum(iphdr, iphdrlen) != 0) {
      kprintf("IP packet dropped due to failing checksum 0x%x\n", inet_chksum(iphdr, iphdrlen));
      stats.ip.chkerr++;
      stats.ip.drop++;
      return -ECHKSUM;
    }
  }
#endif

  // Trim pbuf. This should have been done at the netif layer, but we'll do it anyway just to be sure that its done
  pbuf_realloc(p, ntohs(IPH_LEN(iphdr)));

  // Is this packet for us?
  for (netif = netif_list; netif != NULL; netif = netif->next) {
    if (ip_addr_isany(&netif->ipaddr) ||
        ip_addr_cmp(&iphdr->dest, &netif->ipaddr) ||
       (ip_addr_isbroadcast(&iphdr->dest, &netif->netmask) &&
        ip_addr_maskcmp(&iphdr->dest, &netif->ipaddr, &netif->netmask)) ||
        ip_addr_cmp(&iphdr->dest, IP_ADDR_BROADCAST)) {
          break;
    }
  }

  // If a DHCP packet has arrived on the interface, we pass it up the
  // stack regardless of destination IP address. The reason is that
  // DHCP replies are sent to the IP adress that will be given to this
  // node (as recommended by RFC 1542 section 3.1.1, referred by RFC 2131).
  if (!netif && IPH_PROTO(iphdr) == IP_PROTO_UDP &&
      ((struct udp_hdr *)((char *) iphdr + IPH_HL(iphdr) * 4))->src == DHCP_SERVER_PORT) {
    netif = inp;
  }  
  
  if (netif == NULL) {
    // Packet not for us, route or discard
    if (!ip_addr_isbroadcast(&iphdr->dest, &inp->netmask)) {
      if (ip_forward(p, iphdr, inp) < 0) pbuf_free(p);
    } else {
      pbuf_free(p);
    }
    return 0;
  }

  if ((IPH_OFFSET(iphdr) & htons(IP_OFFMASK | IP_MF)) != 0) {
    kprintf("IP packet dropped since it was fragmented (0x%x).\n", ntohs(IPH_OFFSET(iphdr)));
    stats.ip.opterr++;
    stats.ip.drop++;
    return -EPROTO;
  }
  
  if (iphdrlen > IP_HLEN) {
    kprintf("IP packet dropped since there were IP options.\n");
    stats.ip.opterr++;
    stats.ip.drop++;
    return -EPROTO;
  }

  // Try to see if any raw sockets wants the packet
  rc = raw_input(p, inp);
  if (rc < 0) return rc;
  if (rc > 0) return 0;

  // Send to upper layers
  switch (IPH_PROTO(iphdr)) {
    case IP_PROTO_UDP:
      return udp_input(p, inp);

    case IP_PROTO_TCP:
      return tcp_input(p, inp);

    case IP_PROTO_ICMP:
      return icmp_input(p, inp);
  
    default:
      // Send ICMP destination protocol unreachable unless is was a broadcast
      if (!ip_addr_isbroadcast(&iphdr->dest, &inp->netmask) && !ip_addr_ismulticast(&iphdr->dest)) {
        p->payload = iphdr;
        icmp_dest_unreach(p, ICMP_DUR_PROTO);
      }

      kprintf("Unsupported transport protocol %d\n", IPH_PROTO(iphdr));
      stats.ip.proterr++;
      stats.ip.drop++;
      return -EPROTO;
  }
}

//
// ip_input_dur
//

err_t ip_input_dur(int code, struct pbuf *p) {
  struct ip_hdr *orig_iphdr = (struct ip_hdr *) p->payload;

  if (p->tot_len < sizeof(struct ip_hdr)) {
    kprintf("ip_input_dur: ICMP message too short\n");
    stats.icmp.lenerr++;
    return -EPROTO;
  }

  kprintf("icmp: destination unreachable src=%a dest=%a proto=%d (code %d)\n", &orig_iphdr->src, &orig_iphdr->dest, IPH_PROTO(orig_iphdr), code);
  return -EPROTO;
}

//
// ip_output_if
//
// Sends an IP packet on a network interface. This function constructs the IP header
// and calculates the IP header checksum. If the source IP address is NULL,
// the IP address of the outgoing network interface is filled in as source address.
//

err_t ip_output_if(struct pbuf *p, struct ip_addr *src, struct ip_addr *dest, int ttl, int proto, struct netif *netif) {
  struct ip_hdr *iphdr;
  static unsigned short ip_id = 0;
  
  if (dest != IP_HDRINCL) {
    if (pbuf_header(p, IP_HLEN)) {
      kprintf("ip_output: not enough room for IP header in pbuf\n");
      stats.ip.err++;
      dbg_break();
      return -EBUF;
    }

    iphdr = p->payload;

    IPH_TTL_SET(iphdr, ttl);
    IPH_PROTO_SET(iphdr, proto);
    
    ip_addr_set(&iphdr->dest, dest);

    IPH_VHLTOS_SET(iphdr, 4, IP_HLEN / 4, 0);
    IPH_LEN_SET(iphdr, htons((unsigned short) p->tot_len));
    IPH_OFFSET_SET(iphdr, htons(IP_DF));
    IPH_ID_SET(iphdr, htons(ip_id));
    ip_id++;

    if (ip_addr_isany(src)) {
      ip_addr_set(&iphdr->src, &netif->ipaddr);
    } else {
      ip_addr_set(&iphdr->src, src);
    }

    IPH_CHKSUM_SET(iphdr, 0);

    if ((netif->flags & NETIF_IP_TX_CHECKSUM_OFFLOAD) == 0) {
      IPH_CHKSUM_SET(iphdr, inet_chksum(iphdr, IP_HLEN));
    }
  } else {
    iphdr = p->payload;
    dest = &iphdr->dest;
  }

  stats.ip.xmit++;

  //kprintf("sending IP datagram on %s:\n", netif->name);
  //ip_debug_print(p);

  return netif->output(netif, p, dest);
}

//
// ip_output
//
// Simple interface to ip_output_if. It finds the outgoing network interface and
// calls upon ip_output_if to do the actual work.
//

err_t ip_output(struct pbuf *p, struct ip_addr *src, struct ip_addr *dest, int ttl, int proto) {
  struct netif *netif;
  
  if ((netif = ip_route(dest)) == NULL) {
    kprintf("ip_output: No route to %a\n", &dest);

    stats.ip.rterr++;
    return -EROUTE;
  }

  return ip_output_if(p, src, dest, ttl, proto, netif);
}

void ip_debug_print(struct pbuf *p) {
  struct ip_hdr *iphdr = p->payload;
  unsigned char *payload;

  payload = (unsigned char *) iphdr + IP_HLEN;
  
  kprintf("+-------------------------------+\n");
  kprintf("|%2d |%2d |   %2d  |      %4d     | (v, hl, tos, len)\n", 
          IPH_V(iphdr), 
          IPH_HL(iphdr), 
          IPH_TOS(iphdr), 
          ntohs(IPH_LEN(iphdr)));
  kprintf("+-------------------------------+\n");
  kprintf("|    %5d      |%d%d%d|    %4d   | (id, flags, offset)\n",
          ntohs(IPH_ID(iphdr)),
          ntohs(IPH_OFFSET(iphdr)) >> 15 & 1,
          ntohs(IPH_OFFSET(iphdr)) >> 14 & 1,
          ntohs(IPH_OFFSET(iphdr)) >> 13 & 1,
          ntohs(IPH_OFFSET(iphdr)) & IP_OFFMASK);
  kprintf("+-------------------------------+\n");
  kprintf("|  %3d  |   %2d  |    0x%04x     | (ttl, proto, chksum)\n",
          IPH_TTL(iphdr),
          IPH_PROTO(iphdr),
          ntohs(IPH_CHKSUM(iphdr)));
  kprintf("+-------------------------------+\n");
  kprintf("|  %3ld  |  %3ld  |  %3ld  |  %3ld  | (src)\n",
          ntohl(iphdr->src.addr) >> 24 & 0xFF,
          ntohl(iphdr->src.addr) >> 16 & 0xFF,
          ntohl(iphdr->src.addr) >> 8 & 0xFF,
          ntohl(iphdr->src.addr) & 0xFF);
  kprintf("+-------------------------------+\n");
  kprintf("|  %3ld  |  %3ld  |  %3ld  |  %3ld  | (dest)\n",
          ntohl(iphdr->dest.addr) >> 24 & 0xFF,
          ntohl(iphdr->dest.addr) >> 16 & 0xFF,
          ntohl(iphdr->dest.addr) >> 8 & 0xFF,
          ntohl(iphdr->dest.addr) & 0xFF);
  kprintf("+-------------------------------+\n");
}