/* * Copyright (c) 2006-2007 Graz University of Technology. 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. The names "Graz University of Technology" and "IAIK of Graz University of * Technology" must not be used to endorse or promote products derived from * this software without prior written permission. * * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED 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 LICENSOR 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 ejip.jtcpip; import ejip_old.Net; import ejip_old.Packet; import ejip.jtcpip.util.Debug; import ejip.jtcpip.util.NumFunctions; /** * Represents the network layer. Contains methods for Packet sending and * receiving (including reassembly). Received Packets are then handed to the * upper (transport) layer. * * @see ejip2.jtcpip.TCP * @see ejip2.jtcpip.UDP * * @author Tobias Kellner * @author Ulrich Feichter * @author Christof Rath * @version $Rev: 994 $ $Date: 2009/01/12 23:00:13 $ */ public class IP { /** Default Time To Live Value */ private static final byte TTL = (byte) 128; /** Protocol Constant for IP Header: ICMP */ public static final byte PROT_ICMP = 1; /** Protocol Constant for IP Header: TCP */ public static final byte PROT_TCP = 6; /** Protocol Constant for IP Header: UDP */ public static final byte PROT_UDP = 17; /** * Packet identification for IP Header. Incremented after each send. (From a * security point of view, this is questionable) TODO */ private static short packetID = 0; /** Exception that gets thrown when there is an error in the IP Address */ private static JtcpipException ipException; public static void init() { ipException = new JtcpipException("Invalid IP Address String"); } /** * Stores the content of a fragment into a reassembling payload * * @param rsmblPay * @param fragmtPay */ private static void rsmblStore(Payload rsmblPay, Payload fragmtPay) { int pLen = IPPacket.getLength(fragmtPay) - IPPacket.getIHL(fragmtPay) * 4; int ofs = IPPacket.getFragOfs(fragmtPay) * 2; int maxIndex = NumFunctions.divRoundUp(pLen, 4); if (ofs + maxIndex > rsmblPay.payload.length) { // if (Debug.enabled) // Debug // .println( // "Reassembled Payload would be greater than max. Payload size! // Offset: " // + ofs + " max. index: " + maxIndex // + " Payload.length: " // + rsmblPay.payload.length, Debug.DBG_IP); ICMP.sendDestUnreach(rsmblPay, 4); // fragmentation needed and DF // set; return; } /* * To prevent data overwriting (as for the first call rsmblPay == * fragmtPay) we copy the data back to front */ switch (pLen % 4) {// copy the last bytes but don't overwrite the // previous content case 1: fragmtPay.payload[maxIndex] &= 0xFF000000; rsmblPay.payload[ofs + maxIndex] &= 0x00FFFFFF; rsmblPay.payload[ofs + maxIndex] |= fragmtPay.payload[maxIndex]; break; case 2: fragmtPay.payload[maxIndex] &= 0xFFFF0000; rsmblPay.payload[ofs + maxIndex] &= 0x0000FFFF; rsmblPay.payload[ofs + maxIndex] |= fragmtPay.payload[maxIndex]; break; case 3: fragmtPay.payload[maxIndex] &= 0xFFFFFF00; rsmblPay.payload[ofs + maxIndex] &= 0x000000FF; rsmblPay.payload[ofs + maxIndex] |= fragmtPay.payload[maxIndex]; break; } for (int i = pLen / 4 - 1; i >= 0; i--) rsmblPay.payload[ofs + i] = fragmtPay.payload[i]; // if the offset == 0 the first fragment is stored and we can send an // ICMP message in case of a time out rsmblPay.setOffset(IPPacket.getFragOfs(fragmtPay)); rsmblPay.reassembledBitMap.setBits(IPPacket.getFragOfs(fragmtPay), pLen / 8); if (!IPPacket.isMFSet(fragmtPay)) {// last fragment => calculate bytes // of unfragmented packet rsmblPay.length = pLen + IPPacket.getFragOfs(fragmtPay) * 8; // Only the very last datablock that doesn't fall on an eight byte // boundary _may_ mark a block in the bitmap (else we wait for a // fragment // that holds the complete datablock) if (pLen % 8 > 0) rsmblPay.reassembledBitMap.setBit(IPPacket .getFragOfs(fragmtPay) + (pLen / 8)); } if (Debug.enabled && (Debug.dbgFlagsToDisplay & Debug.DBG_IP) > 0) rsmblPay.reassembledBitMap.print(); } /** * Handles the reassembling of a IP packet fragment * * @param pay */ private static void reassemblePayload(Payload pay) { for (int i = 0; i < StackParameters.PAYLOAD_POOL_SIZE; i++) { if (Payload.pool[i] != null && Payload.pool[i].getStatus() == Payload.PAYLOAD_RESMBL && (IPPacket.getID(pay) == IPPacket.getID(Payload.pool[i]))) { // just // received // a // packet // with // the // same // ID rsmblStore(Payload.pool[i], pay); Payload.freePayload(pay); // if (Debug.enabled) // if (Payload.pool[i].length != -1) // Debug.println("Seeing if all " // + NumFunctions.divRoundUp( // Payload.pool[i].length, 8) // + " bits are set", Debug.DBG_IP); if (Payload.pool[i].length != -1 // length is -1 until we got // the last fragment. && Payload.pool[i].reassembledBitMap .allSet(NumFunctions.divRoundUp( Payload.pool[i].length, 8))) { Payload.pool[i].setStatus(Payload.PAYLOAD_USED, 0); //handlePayload(Payload.pool[i]); byte prot = IPPacket.getProtocol(Payload.pool[i]); switch (prot) { case PROT_TCP: TCP.receivePayload(Payload.pool[i]); break; case PROT_UDP: UDP.receivePayload(Payload.pool[i]); break; case PROT_ICMP: ICMP.receivePayload(Payload.pool[i]); break; } } return; } } // No matching payload found => this is the first part of a fragmented // payload pay.length = -1; // the payload length is invalid until we got the // last fragment pay.setStatus(Payload.PAYLOAD_RESMBL, StackParameters.REASSEMBLE_TIMEOUT); pay.setOffset(StackParameters.PAYLOAD_MAX_DATA_SIZE); // No fragments // stored yet rsmblStore(pay, pay); // move the payload to the correct position } /** * Copies the data stored in a <code>Payload</code> object to a * <code>Packet</code> object. Also sets the protocol field in the * Ethernet header to IP. * * @param pay * Payload to copy from * @param p * Packet to write to * * @return Zero if the full payload fitted into the packet else the number * of already copied bytes */ protected static int payloadToPacket(Payload pay, Packet p) { return payloadToPacket(pay, p, 0); } /** * Copies the data (up to the size of a Ethernet packet) stored in a * <code>Payload</code> object to a <code>Packet</code> object. Also * sets the protocol field in the Ethernet header to IP. * * @param pay * Payload to copy from * @param p * Packet to write to * @param offset * in 4-byte-blocks (i.e. already sent blocks) * @return The offset where the data for the next Packet starts (if * fragmentation needed, else 0) */ protected static int payloadToPacket(Payload pay, Packet p, int offset) { byte headerLength = IPPacket.getIHL(pay); boolean isFragmt = (pay.length + headerLength * 4) > p.buf.length * 4 && pay.getStatus() != Payload.PAYLOAD_ARP_WT; if (isFragmt && IPPacket.isDFSet(pay)) { ICMP.sendDestUnreach(pay, 4); // Code 4: fragmentation needed and // DF set return 0; } /* * if (isFragmt && (offset % 2 != 0)) //Should not be needed anymore { * if (Debug.enabled) Debug.println("ALERT! Fragment not on boundary", * Debug.DBG_IP); offset--; // Fragmentation Offset not on an 8-byte * boundary -> overlap } */ int dataInPayload = headerLength * 4 + pay.length - offset * 4; // Size // of // the // (rest // of // the) // payload p.len = Math.min(p.buf.length * 4, dataInPayload); p.llh[6] = 0x0800; // IP code for ethernet header if (isFragmt) // IP Fragmentation! { if (p.len == dataInPayload) // Last fragment of the Payload IPPacket.clearMF(pay); else { p.len -= (p.len - headerLength * 4) % 8; // Fragmentation // offset - 8 byte // boundary! IPPacket.setMF(pay); } IPPacket.setLength(pay, (short) p.len); IPPacket.setFragOfs(pay, (short) (offset / 2)); } IPPacket.setChecksum(pay); // copy the IP header into the Ethernet packet for (byte i = 0; i < headerLength; i++) p.buf[i] = pay.ipHeader[i]; int i = 0; int lastPayloadIndex = NumFunctions.divRoundUp(pay.length, 4); int limit = Math.min(NumFunctions.divRoundUp(p.len, 4) - headerLength, lastPayloadIndex - offset); for (i = 0; i < limit; i++) p.buf[i + headerLength] = pay.payload[offset + i]; // TODO: maybe padding if too short if (p.len == dataInPayload) return 0; return offset + 1; // return p.len == dataInPayload ? 0 : offset + i; // if the size of the // (rest of the) payload // == the packet length // return 0 else the // offset } /** * Takes a newly received Packet from the network driver. The content is * then stored in a <code>Payload</code> object. If the Packet is valid * (checksum, ehternet header...) it will be delivered to to the next layer * (via {@link IP#handlePayload}). If a packet is fragmented, it will be * reassembled here. <b>Note:</b> After the method returns, the passed * Packet will be marked as FREE! * * @see #handlePayload * @param p * Packet to process */ public static void receivePacket(Packet p) { if (Debug.enabled) Debug.println("Received a packet", Debug.DBG_IP); if (p.len < 20) { if (Debug.enabled) Debug.println("Packet is shorter than 20 bytes, dropping!!", Debug.DBG_IP); p.setStatus(Packet.FREE); return; } if (p.llh[6] != 0x0800) { if (Debug.enabled) Debug.println("PACKET IS NOT AN IP PACKET!!", Debug.DBG_IP); p.setStatus(Packet.FREE); return; } Payload pay = Payload.newPayload(); if (pay == null) { if (Debug.enabled) Debug.println("No more Payloads available - packet dropped", Debug.DBG_IP); p.setStatus(Packet.FREE); return; } for (int i = 0; i < 5; i++) // copy first 20 byte of header to ipHeader (therefore we are not // able do read out IPPacket.getIHL(pay)) pay.ipHeader[i] = p.buf[i]; // accept only packets for our ip or eth broadcasts if (IPPacket.getDestAddr(pay) != Net.linkLayer.getIpAddress() && !(p.llh[0] == 0xFFFF && p.llh[1] == 0xFFFF && p.llh[2] == 0xFFFF)) { // System.out.println(IPPacket.getDestAddr(pay)); // System.out.println(Net.linkLayer.ip); if (Debug.enabled) Debug.println("Packet is not for us... dropping", Debug.DBG_IP); p.setStatus(Packet.FREE); Payload.freePayload(pay); return; } int headerLength = (int) (IPPacket.getIHL(pay) & 0xFF); if (headerLength > 5) // if header is longer than 20 byte copy the // rest for (int i = 5; i < headerLength; i++) pay.ipHeader[i] = p.buf[i]; // if (Debug.enabled) // Debug.println("Captured packet length: ", Debug.DBG_IP); // check if the packet length is acceptable, drop the packet if wrong int payloadLengtFromIPHeader = ((int) (IPPacket.getLength(pay) & 0xFFFF) - headerLength * 4); if (payloadLengtFromIPHeader > p.len - headerLength * 4 || payloadLengtFromIPHeader > StackParameters.PAYLOAD_MAX_DATA_SIZE) { if (Debug.enabled) Debug.println("Packet is too long, dropping", Debug.DBG_IP); p.setStatus(Packet.FREE); Payload.freePayload(pay); return; } for (int i = 0; i < (NumFunctions.divRoundUp(p.len, 4) - headerLength); i++) // copy // payload { pay.payload[i] = p.buf[i + headerLength]; // if (Debug.enabled) // Debug.print(Debug.intToHexString(pay.payload[i]), Debug.DBG_IP); } // Take the min of the length specified in the IP header and the real // captured data as Payload length pay.length = payloadLengtFromIPHeader; p.setStatus(Packet.FREE); if (!IPPacket.isValidPacket(pay)) { if (Debug.enabled) Debug.println("Checksum not valid; dropping packet", Debug.DBG_IP); Payload.freePayload(pay); return; } if (IPPacket.isMFSet(pay) || IPPacket.getFragOfs(pay) != 0) { if (Debug.enabled) Debug.println("Valid Packet, FRAGMENTED", Debug.DBG_IP); reassemblePayload(pay); } else { // not fragmented if (Debug.enabled) Debug.println("Valid Packet, not fragmented", Debug.DBG_IP); // TODO: inlined //handlePayload(pay); byte prot = IPPacket.getProtocol(pay); switch (prot) { case PROT_TCP: TCP.receivePayload(pay); break; case PROT_UDP: UDP.receivePayload(pay); break; case PROT_ICMP: ICMP.receivePayload(pay); break; } } /* * if (Debug.enabled){ Debug.println("version " + * IPPacket.getVersion(pay)); Debug.println("ihl " + * IPPacket.getIHL(pay)); Debug.println("tos " + * Debug.IntegerToHexString(IPPacket.getToS(pay))); * Debug.println("length: " + IPPacket.getLength(pay)); * Debug.println("id: " + IPPacket.getID(pay)); Debug.println("res: " + * IPPacket.isReservedSet(pay)); Debug.println("fragm: " + * IPPacket.isDFSet(pay)); Debug.println("more frag: " + * IPPacket.isMFSet(pay)); Debug.println("frag ofs: " + * IPPacket.getFragOfs(pay)); Debug.println("ttl: " + * IPPacket.getTTL(pay)); Debug.println("protocol: " + * Debug.IntegerToHexString(IPPacket.getProtocol(pay))); * Debug.println("src: " + * Debug.IntegerToHexString(IPPacket.getSrcAddr(pay))); * Debug.println("dest addr: " + * Debug.IntegerToHexString(IPPacket.getDestAddr(pay))); } */ } /** * Prepares the IP header. In order to send a packet * * @param pay * The packet payload * @param destIP * The destination IP * @param protocol * The transport protocol id */ private static void prepareIPPacket(Payload pay, int destIP, byte protocol) { IPPacket.setIHL(pay, (byte) 0x5); IPPacket.setToS(pay, (byte) 0x0); IPPacket .setLength(pay, (short) (pay.length + IPPacket.getIHL(pay) * 4)); IPPacket.setID(pay, packetID++); IPPacket.clearDF(pay); IPPacket.clearMF(pay); IPPacket.clearReserved(pay); IPPacket.setFragOfs(pay, (short) 0x0); IPPacket.setTTL(pay, (byte) TTL); IPPacket.setProtocol(pay, protocol); if ((destIP >= 2130706432) && (destIP <= 2147483647)) // 127.0.0.0/8 { IPPacket.setDestAddr(pay, Net.linkLayer.getIpAddress()); // Swapping // addresses for // loopback IPPacket.setSrcAddr(pay, destIP); } else { IPPacket.setDestAddr(pay, destIP); IPPacket.setSrcAddr(pay, Net.linkLayer.getIpAddress()); } switch (IPPacket.getProtocol(pay)) { case PROT_TCP: TCPPacket.setChecksum(pay); break; case PROT_UDP: UDPPacket.setChecksum(pay); break; case PROT_ICMP: ICMPPacket.setChecksum(pay); break; } } /** * @deprecated * * Sends a Packet by handing it to the network driver. Takes a payload, a * destination ip and a protocol number (e.g. <code>IP.PROT_TCP</code>). * <b>Note:</b> After the method returns, the passed Payload will be marked * as free! * * @param pay * The packet payload * @param destIP * The destination IP * @param protocol * The transport protocol id * * @return Whether send was successful */ public static boolean sendPayload(Payload pay, int destIP, byte protocol) { prepareIPPacket(pay, destIP, protocol); if (pay.length > StackParameters.PACKET_MTU_SIZE) { // TODO: Implement fragmentation here? Would this make sense? if (Debug.enabled) Debug.println("Fragmentation not implemented here!", Debug.DBG_IP); return false; } Packet p = Packet.getPacket(Packet.FREE, Packet.ALLOC, Net.linkLayer); if (p == null) return false; // TODO: Retransmit payloadToPacket(pay, p); Payload.freePayload(pay); p.setStatus(Packet.SND_DGRAM); return true; } /** * Prepares and marks a Payload for sending * * @param pay * The packet payload * @param destIP * The destination IP * @param protocol * The transport protocol id */ public synchronized static void asyncSendPayload(Payload pay, int destIP, byte protocol) { if (Debug.enabled) Debug.println("asyncSendPayload", Debug.DBG_IP); prepareIPPacket(pay, destIP, protocol); if (Debug.enabled) Debug.println("asyncSendPayload2", Debug.DBG_IP); pay.setStatus(Payload.PAYLOAD_SND_RD, 0); if (Debug.enabled) Debug.println("asyncSendPayload3", Debug.DBG_IP); } /** * Converts an IP address of the format [xx]x.[xx]x.[xx]x.[xx]x to an * integer. Throws an exception if the format of the String is faulty. * * @param ipAddr * The IP address String * @return The IP address as an int * @throws JtcpipException * Invalid IP Address String */ public static int ipStringToInt(String ipAddr) throws JtcpipException { byte dots = 0; short ipOctet = 0; int ipInt = 0; for (int i = 0; i <= ipAddr.length(); i++) { if (i == ipAddr.length() || ipAddr.charAt(i) == '.') { if (i < ipAddr.length() && ++dots == 4) { // if (Debug.enabled) // Debug.println("Too many dots in " + ipAddr, // Debug.DBG_IP); throw ipException; } if (ipOctet < 0 || ipOctet > 255) { // if (Debug.enabled) // Debug.println("Wrong IP values in " + ipAddr, // Debug.DBG_IP); throw ipException; } ipInt = (ipInt << 8) | (ipOctet & 0xFF); ipOctet = 0; } else if (ipAddr.charAt(i) >= '0' && ipAddr.charAt(i) <= '9') ipOctet = (short) (ipOctet * 10 + (ipAddr.charAt(i) - '0')); else { if (Debug.enabled) Debug.println("Wrong char in IP address ", Debug.DBG_IP); throw ipException; } } if (dots != 3) { if (Debug.enabled) Debug.println("IP address too short ", Debug.DBG_IP); throw ipException; } return ipInt; } /** * @param ipAddr * @return the String representation of an IP address */ public static String ipIntToString(int ipAddr) { return "" + ((ipAddr >>> 24) & 0xFF) + "." + ((ipAddr >>> 16) & 0xFF) + "." + ((ipAddr >>> 8) & 0xFF) + "." + ((ipAddr) & 0xFF); } }