/* * Copyright (c) Martin Schoeberl, martin@jopdesign.com * All rights reserved. * * 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. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by Martin Schoeberl * * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. * */ package ejip123; import ejip123.util.Dbg; /** see RFC 826. */ class Arp{ private final static int ENTRY_CNT = 4; private static final Entry[] list; public static final int ETHER_PROT = 0x0806; private Arp(){ } /** Adds an entry into the ARP table. @param p A received ARP request or reply */ private static void add(Packet p){ int ip_src = (p.buf[3] << 16) + (p.buf[4] >>> 16); int nr = -1; int oldest = list[0].age; int youngest = oldest; for(int i = 0; i < ENTRY_CNT; ++i){ if(list[i].ip == ip_src){ // we have an entry for this IP address nr = i; } if(list[i].age < oldest){ oldest = list[i].age; } else if(list[i].age > youngest){ youngest = list[i].age; } } // if there is no previous entry for that IP, replace the oldest entry Entry e = list[(nr == -1) ? oldest : nr]; e.ip = ip_src; e.mac[0] = p.buf[2] >>> 24; e.mac[1] = (p.buf[2] >>> 16)&0xff; e.mac[2] = (p.buf[2] >>> 8)&0xff; e.mac[3] = (p.buf[2])&0xff; e.mac[4] = (p.buf[3] >>> 24); e.mac[5] = (p.buf[3] >>> 16)&0xff; e.valid = true; e.age = ++youngest; dump(nr); } private static Entry find(int ip){ for(int i = 0; i < ENTRY_CNT; ++i){ if(list[i].ip == ip && list[i].valid){ return list[i]; } } return null; } private static void dump(int nr){ Dbg.wr("add ARP IP="); Dbg.hexVal(list[nr].ip); for(int i = 0; i < 6; ++i){ Dbg.hexVal(list[nr].mac[i]); } } private static class Entry{ private int ip; private final int[] mac; // could be optimized to use 16-bit words // in intel byte order for CS8900 private boolean valid; private int age; // TODO: not used - age wraps around after 4 billion requests // static int ageCnt; Entry(){ mac = new int[6]; ip = 0; valid = false; age = 0; } } static{ list = new Entry[ENTRY_CNT]; for(int i = 0; i < ENTRY_CNT; ++i) list[i] = new Entry(); // Static ARP entry: IP Broadcast -> Ethernet Broadcast // this is just one possibility, should be resolved algorithmically not with the table // solved with LinkLayer.isLocalBroadcast(ip) /* list[0].ip = 0xFFFFFFFF; // 255.255.255.255 list[0].mac[0] = 0xFF; // -> resolves to FF:FF:FF:FF:FF:FF list[0].mac[1] = 0xFF; list[0].mac[2] = 0xFF; list[0].mac[3] = 0xFF; list[0].mac[4] = 0xFF; list[0].mac[5] = 0xFF; list[0].valid = true; list[0].age = 1; */ } /** process an ARP packet. */ static void receive(Packet p, int[] eth, int ip, int[] ethdr){ // Dbg.wr('A'); if(p.buf[6] == ip){ // System.out.println("ARP receive"); int arp_op = p.buf[1]&0xffff; add(p); // Add the entry anyway if(arp_op == 1){ // System.out.println("request"); /* Set the ar$op field to ares_op$REPLY */ p.buf[1] = 0x06040002; // hw-len, sw-len, opcode reply /* Swap hardware and protocol fields, putting the local hardware and protocol addresses in the sender fields. */ int ip_src = (p.buf[3] << 16) + (p.buf[4] >>> 16); p.buf[2] = (eth[0] << 24) + (eth[1] << 16) + (eth[2] << 8) + eth[3]; p.buf[3] = (eth[4] << 24) + (eth[5] << 16) + (ip >>> 16); // llh // p.buf[4] = (ip << 16) + p.llh[3]; // p.buf[5] = (p.llh[4] << 16) + p.llh[5]; p.buf[4] = (ip << 16) + ethdr[3]; p.buf[5] = (ethdr[4] << 16) + ethdr[5]; p.buf[6] = ip_src; /* Send the packet to the (new) target hardware address on the same hardware on which the request was received. */ p.setLen(46); // llh /* p.llh[0] = p.llh[3]; p.llh[1] = p.llh[4]; p.llh[2] = p.llh[5]; p.llh[6] = ETHER_PROT; // ARP frame */ ethdr[0] = ethdr[3]; ethdr[1] = ethdr[4]; ethdr[2] = ethdr[5]; ethdr[6] = ETHER_PROT; // ARP frame p.setStatus(Packet.DGRAM_RDY); // mark packet ready to send return; } } p.free(); // mark packet free } /** Send an ARP request from LinkLayer to the source address from the IP packet. The original IP packet gets thrown away. */ private static void sendRequest(Packet p){ int ip_dest = p.buf[4]; p.buf[0] = 0x00010800; // hw addr. space 1, Protocol add. space IP p.buf[1] = 0x06040001; // hw-len, sw-len, opcode request int eth[] = CS8900.eth; // we have only the static field in CS8900 p.buf[2] = (eth[0] << 24) + (eth[1] << 16) + (eth[2] << 8) + eth[3]; p.buf[3] = (eth[4] << 24) + (eth[5] << 16) + (CS8900.single.getIp()>>> 16); p.buf[4] = (CS8900.single.getIp()<< 16); // we don't know the dest. eth. addr. p.buf[5] = 0; p.buf[6] = ip_dest; // destination IP address p.setLen(46); // why 46? // llh // p.llh[0] = 0xffff; // Ethernet broadcast // p.llh[1] = 0xffff; // p.llh[2] = 0xffff; CS8900.ethdr[0] = 0xffff; // Ethernet broadcast CS8900.ethdr[1] = 0xffff; CS8900.ethdr[2] = 0xffff; // own Ethernet address is filled by CS8900 // p.llh[6] = ETHER_PROT; // ARP frame CS8900.ethdr[6] = ETHER_PROT; } /** Fill in the destination MAC address. If not in the cache use this packet for a ARP request. The IP packet gets lost when not a TCP packet. @return the original or created copy for the link layer to send. Can be null! - nah */ static Packet fillMAC(Packet p, int[] ethdr){ Entry e; // IP destination address (without gateway) is // at position 4 for IP packets and at 6 for ARP packets // FIXME do we really need to find() the dest mac for arp packets? // if we answer a request, we know the mac of the sender // if we request a mac, we need to broadcast // are there any other cases? if(p.prot() == ETHER_PROT){ e = find(p.buf[6]); } else{ // TODO check for local broadcast... // if(CS8900.single.isLocalBroadcast(ip)) e = find(p.buf[4]); } // TODO: for dhcp // int firstHopDest = CS8900.isSameSubnet(p.buf[addrPos]) ? // p.buf[addrPos] : Ip.linkLayer.gateway; // int firstHopDest = p.buf[addrPos]; // Entry e = Entry.find(firstHopDest); if(e == null){ // If it's a TCP packet we need to make a copy, // set the status to on-the-fly and rely on the // TCP timeout retransmission TODO: avoid the need to copy if(p.status() == Packet.CON_RDY){ Packet ap = PacketPool.getFreshPacket(); if(ap != null){ ap.copy(p); sendRequest(ap); // avoid transmit from the link layer again // before the ARP reply comes in p.setStatus(Packet.CON_ONFLY); return ap; } } else{ sendRequest(p); } } else{ int[] mac = e.mac; // intel byte order !!! // p.llh[0] = (mac[0] << 8)|mac[1]; // p.llh[1] = (mac[2] << 8)|mac[3]; // p.llh[2] = (mac[4] << 8)|mac[5]; ethdr[0] = (mac[0] << 8)|mac[1]; ethdr[1] = (mac[2] << 8)|mac[3]; ethdr[2] = (mac[4] << 8)|mac[5]; } return p; } /** Returns true if the MAC address of a given IP address is already in the cache @return boolean */ public static boolean inCache(int ip){ // TODO dhcp // return Entry.find(CS8900.isSameSubnet(ip) ? ip : // Ip.linkLayer.gateway) != null; return find(ip) != null; } }