/* * 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.legacy.Html; import ejip123.util.Dbg; import joprt.RtThread; /** The internet protocol (see RFC 791). */ public class Ip{ public final static int OFFSET = 5; public final static int ETHER_PROT = 0x0800; private final static int ttl = (64<<24); private static int ip_id = 0x12340000; //TODO? provide a default mtu for upper layers //is Router.getIf(dstIp).getMtu() enough? private Ip(){ } /** Creates a thread for receiving packets, sending fragments and other periodic work. @param prio Priority @param us Period in microseconds */ public static void init(int prio, int us){ new RtThread(prio, us){ public void run(){ for(; ;){ waitForNextPeriod(); loop(); } } }; } private static void loop(){ // is a received packet in the pool? Packet p = PacketPool.getReceivedPacket(); if(p != null){ receive(p); } int cur = (int)System.currentTimeMillis(); Icmp.loop(cur); Reassembler.loop(cur); Tcp.loop(cur); Fragmenter.loop(); } private static void receive(Packet p){ int[] buf = p.buf; int i = buf[0]; if((i&0xF0000000) != 0x40000000){ // !ipv4? p.free(); return; } int dstIp = buf[4]; LinkLayer linkLayer = p.linkLayer(); // we drop all packets that are not addressed to us, aren't a broadcast and aren't from ourselves (loopback). if(((linkLayer != null) || (dstIp&0xFF000000) != 0x7F000000) && linkLayer.getIp() != dstIp && (!linkLayer.isLocalBroadcast( dstIp))){ p.free(); return; } if(chkSum(buf, 0, 20) != 0){ p.free(); return; } int len = i&0xffff; if(len < p.len()) // if ip.len < linklayer.len truncate it, maybe upper layer can use it anyway. p.setLen(len); int off = (i&0x0F000000)>>>24; // fragmentation reassembly if((p = Reassembler.process(p, off)) == null) return; int prot = (buf[2]>>>16)&0xff; // protocol switch(prot){ case Icmp.PROTOCOL: // p.print(0); Icmp.receive(p, off); break; case Tcp.PROTOCOL: if((buf[5]&0xffff) == 80){ // still do our simple HTML server Html.doTCP(p); send(p, dstIp, buf[3], prot); } else{ // simulate receive packet loss // if((System.currentTimeMillis()&0x1000) != 0){ // Dbg.wr("packet lost\n"); // p.free(); // }else Tcp.process(p, off); } break; case Udp.PROTOCOL: Udp.process(p, off); break; default: Icmp.sendProtocolUnreachable(buf, off); p.free(); break; } } /** Sends an IP packet. The IP of the sending interface will be used as source IP. @param p The packet to be sent. @param dstIp Destination IP. @param prot Protocol (e.g. udp, tcp, icmp). @return true if a packet was sent to a link layer */ static boolean send(Packet p, int dstIp, int prot){ return send(p, 0, dstIp, prot); } /** Sends an IP packet. If the source IP is set to 0, the IP of the sending interface will be used. @param p The packet to be sent. @param srcIp Source IP. If 0, the IP of the used interface will be used. @param dstIp Destination IP. @param prot Protocol (e.g. udp, tcp, icmp). @return true if a packet was sent to a link layer. */ static boolean send(Packet p, int srcIp, int dstIp, int prot){ int[] buf = p.buf; int len = p.len(); if(len == 0){ p.free(); // mark packet free return false; } buf[0] = 0x45000000 + len; // ip length (header without options) buf[1] = getId(); // identification, no fragmentation (yet) buf[2] = ttl + (prot<<16); // ttl, protocol, clear checksum buf[4] = dstIp; // test for loopback destination if((dstIp&0xFF000000) == 0x7F000000){ // check and set source ip if((srcIp&0xFF000000) != 0x7F000000){ if(srcIp == 0) buf[3] = 0x7F000001; else{ p.free(); return false; } } else buf[3] = srcIp; buf[2] |= chkSum(buf, 0, 20); p.setLinkLayer(null); p.setStatus(Packet.RCV); // sending == setting as received for loopback connections return true; } LinkLayer ll = p.linkLayer(); if(ll == null){ ll = Router.getIf(dstIp); if(ll == null){ p.free(); return false; } else p.setLinkLayer(ll); } // check if source ip is ok rfc1122 3.2.1.3 if(srcIp == 0) srcIp = ll.getIp(); // TODO if the interface is down, this could be 0. else{ // not allowed as source ip: limited bc, local bc, loopback addresses if(srcIp == 0xFFFFFFFF || ((srcIp&0xFF000000) == 0x7F000000) || ll.isLocalBroadcast(srcIp)){ p.free(); return false; } } buf[3] = srcIp; p.setProt(ETHER_PROT); if(len > ll.getMtu()){ if(p.isConPrep()){ p.free(); return false; } Fragmenter.frag(p); } else{ buf[2] |= chkSum(buf, 0, 20); p.setRdy(); } return true; } /** calcs ip check sum. assumes (32 bit) word boundaries. rest of buffer needs to be 0-padded. @param buf buffer to be checked @param off offset in buffer (in words) @param cnt length in bytes @return checksum */ public static int chkSum(int[] buf, int off, int cnt){ // TODO make package visible when Html is ported if(off < 0 || cnt < 0) return 0; cnt = (cnt + 3)>>2; // word count int sum = 0; while(cnt != 0){ int i = buf[off]; sum += i&0xffff; sum += i>>>16; ++off; --cnt; } while((sum>>16) != 0) sum = (sum&0xffff) + (sum>>16); sum = (~sum)&0xffff; return sum; } /** Returns a number (in the upper 16 bit) to be used as IP id. @return An integer different than the calls before (wraps after 2^16 calls). */ private static int getId(){ ip_id += 0x10000; return ip_id; } /** Converts 4 unsigned bytes (like in dot format) to an IP address integer. @param msb most significant byte. @param smsb second most significant byte. @param tmsb third most significant byte. @param lsb least significant byte. @return An integer to be used as IP address in related classes. */ public static int Ip(int msb, int smsb, int tmsb, int lsb){ return (msb<<24) + (smsb<<16) + (tmsb<<8) + lsb; } static int getMaxPayload(int dstIp){ return Router.getIf(dstIp).getMtu() - (OFFSET<<2); } static int getSrcIp(int dstIp){ return Router.getIf(dstIp).getIp(); } public static int parseIp(CharSequence cmd, int off){ int len = cmd.length(); while(off < len && cmd.charAt(off) == ' ') off++; if(off >= len || !Character.isDigit(cmd.charAt(off))) return 0; int start = off; off++; int curOctet = 3; int ip = 0; int digitsDone = 1; // ping 192.168.0. // 01234567890123456 while(off < len){ char c = cmd.charAt(off); // Dbg.wr(c); boolean last = off == (len - 1); int end; if(c == '.' || (curOctet == 0 && (digitsDone > 0 && !Character.isDigit(c)) || digitsDone == 3)){ // Dbg.wr("digits done="); // Dbg.intVal(digitsDone); if(digitsDone == 0) return 0; end = off; } else if(last){ // Dbg.wr("last "); if(!Character.isDigit(c) || curOctet != 0) return 0; end = off + 1; } else if(Character.isDigit(c)){ digitsDone++; off++; continue; } else return 0; // Dbg.lf(); // Dbg.intVal(off); // Dbg.intVal(start); // Dbg.intVal(end); int octet = 0; int j = start; while(j < end){ int digit = Character.digit(cmd.charAt(j), 10); if(digit < 0 || digit > 9) return 0; octet *= 10; octet += digit; j++; } // Dbg.intVal(octet); // Dbg.lf(); if(octet > 255) return 0; ip += octet<<(curOctet<<3); curOctet--; if(curOctet < 0) break; digitsDone = 0; off++; start = off; } if(curOctet >= 0) return 0; // Dbg.lf(); return ip; } /** Fragments packets into smaller junks. If the underlying linklayer can't send the given packet as a whole, this class splits it up. */ private static class Fragmenter{ private static Packet bigP = null; private static int todo = 0; private static int done = 0; private static final int NetOffB = OFFSET<<2; private static int maxPayload = 0; private Fragmenter(){ } static void loop(){ if(bigP == null) return; Packet p; if((p = PacketPool.getFreshPacket()) == null) return; int[] buf = p.buf; int[] Bbuf = bigP.buf; buf[1] = Bbuf[1]|(done>>3); // set offset int byteCnt; // how much can we transfer with this segment if(todo <= maxPayload){ // last fragment? byteCnt = todo; todo = 0; } else{ byteCnt = maxPayload&0xFFFFFFF8; // round down to next 8B boundary todo -= byteCnt; buf[1] |= 0x2000; // set more fragments flag } int lenIP = byteCnt + NetOffB; p.setLen(lenIP); buf[0] = 0x45000000 + lenIP; // ip length (header without options) lenIP = lenIP>>2; int i; for(i = 2; i <= 4; i++){ // copy IPs; ttl, prot, clear checksum buf[i] = Bbuf[i]; } int j; for(i = OFFSET, j = i + (done>>2); i <= lenIP; i++, j++){ // copy payload buf[i] = Bbuf[j]; } buf[2] |= chkSum(buf, 0, 20); p.setLinkLayer(bigP.linkLayer()); p.setProt(ETHER_PROT); p.setStatus(Packet.DGRAM_RDY); // mark packet ready to send if(todo <= 0){ // TODO what about CON packets? Bbuf[1] |= 0x2000; Bbuf[2] |= chkSum(Bbuf, 0, 20); bigP.setStatus(Packet.DGRAM_RDY); bigP = null; } else done += byteCnt; } public static void frag(Packet p){ if(bigP != null){// we support one concurrent big packet only atm p.free(); } else{ bigP = p; maxPayload = p.linkLayer().getMtu() - NetOffB; done = maxPayload&0xFFFFFFF8; todo = p.len() - NetOffB - done; int len = done + NetOffB; p.buf[0] = 0x45000000 + len; p.setLen(len); } } } /** packet reassembly as suggested by http://tools.ietf.org/html/rfc815 . */ private static class Reassembler{ /* layout of a hole descriptor: 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-hole--+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | next pointer | | hole.first | hole.last | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ private static Packet asm = null; private static int[] asmbuf; private static int off; private static int timestamp = 0; private static int asmTimeout = 3000; private static final Object mutex = new Object(); private Reassembler(){ } static void loop(int cur){ if(asm != null && timestamp + asmTimeout - cur < 0){ if(getHead() > off + 2){ asmbuf[1] = (asmbuf[1]&(0xffff0000))|0x2000; // restore original flags and offset Icmp.sendTimeExceeded(asmbuf, off); } asm.free(); asm = null; timestamp = 0; } } private static boolean reassemble(int[] buf){ boolean mf = (buf[1]&0x2000) == 0x2000; int bytesFilled = 0; int cur = getHead(); int ffirst = (buf[1]&0x1fff)<<3; // offset in bytes relative to the beginning of the ip payload int b0 = buf[0]; // the last byte of this fragment is ffirst + total length - hdr length - 1 int flast = ffirst + ((b0&0xffff) - (((b0>>>24)&0xf)<<2) - 1); // Dbg.wr("ffirst=" + ffirst + " flast=" + flast); int prev = 0; do{ // Dbg.wr("cur=" + cur); int hlast = asmbuf[cur + off + 1]&0xffff; if(ffirst > hlast){ // begin of the fragment is after the end of the hole // Dbg.wr("ffirst(" + ffirst + ") > (" + hlast + ") hlast"); continue; } int hfirst = asmbuf[cur + off + 1]>>>16; // Dbg.wr("hfirst=" + hfirst + " hlast=" + hlast); if(flast < hfirst){ // end of the fragment is before the begin of the hole // Dbg.wr("flast (" + flast + ") < (" + hfirst + ") hfirst"); continue; } if(ffirst > hfirst){ // we fill the rear part of the hole // Dbg.wr("ffirst > hfirst"); // if head points at this hole, it will so after setting the new borders too setHole(hfirst, ffirst - 1, nextHole(cur)); bytesFilled += hlast - ffirst; } if(flast < hlast){ // we fill the front of the hole // Dbg.wr("flast < hlast"); if(mf){ // if this is the first hole in the list we need to correct the head pointer if(cur == getHead()){ // Dbg.wr("chg head\n"); setHead(flast + 1); } else{ // we need to correct the link pointing here // Dbg.wr("chg prev\n"); asmbuf[prev + off] = (flast + 1)>>2; } setHole(flast + 1, hlast, nextHole(cur)); } bytesFilled += flast - hfirst + 1; } // Dbg.wr("end of loop"); prev = cur; } while((cur = nextHole(cur)) != 0); // Dbg.wr(Integer.toHexString(getHead()) + " " + Integer.toHexString(prev) + " " + Integer.toHexString(cur)); int j = ((ffirst + 3)>>2) + off; int max = ((flast + 3)>>2) + off; // Dbg.wr("von " + j + " bis " + max + " = " + (max - j)); if(max >= asmbuf.length){ Dbg.wr("can not reassemble the whole packet. its too long!\n"); asm.free(); asm = null; return false; } for(int i = off; j <= max; j++, i++){ asmbuf[j] = buf[i]; } // DONE what happens if we fill (a part of) a hole multiple times? // asm.setLen(asm.len() + flast - ffirst + 1); asm.addToLen(bytesFilled); // Dbg.wr("that packet filled "); // Dbg.intVal(bytesFilled); // Dbg.wr("bytes\n"); return getHead() == prev; } /** next list item. @param cur index of the current hole in buf @return index of the next hole descriptor or 0, if cur is the last */ public static int nextHole(int cur){ return asmbuf[off + cur]&0xffff; } private static int getHead(){ return asmbuf[1]&0xffff; } private static void setHole(int first, int last, int next){ // Dbg.wr("first=" + Integer.toHexString(first) + "=" + first); first >>= 2; // Dbg.wr("wrting to " + first); // Dbg.wr("hfirst=" + Integer.toHexString((first<<18)&0xffff0000) + " hlast=" + last + " next=" + (next>>2)); asmbuf[first + off] = next>>2; asmbuf[first + off + 1] = (first<<18)|last; // Dbg.wr("firstlast=" + Integer.toHexString(asmbuf[first + off + 1])); } private static void setHead(int newHead){ // Dbg.wr("changing head pointer to " + (newHead>>2)); asmbuf[1] = (asmbuf[1]&(0xffff0000))|(newHead>>2); // Dbg.wr("new head=" + getHead()); } private static boolean relatedPacket(int[] rcvbuf){ return rcvbuf[3] == asmbuf[3] // src ip && (rcvbuf[1]&0xffff0000) == (asmbuf[1]&0xffff0000) // id && (rcvbuf[2]&0x00ff0000) == (asmbuf[2]&0x00ff0000) // transport prot && (rcvbuf[4] == asmbuf[4]); // dst ip } /** Reassembles a packet. @param p a received, potentially fragmented packet. @param offset Offset where IP payload begins. @return a complete packet (either if called with the last missing fragment or an unfragmented Packet) or null, if the packet could not be reassembled (yet). */ public static Packet process(Packet p, int offset){ int[] buf = p.buf; if((buf[1]&0x3FFF) == 0) // packet is not a fragment return p; Packet ret = null; synchronized(mutex){ if(asm == null){ // first fragment and no other packet reassembly ongoing // Dbg.wr("first fragment... "); asm = PacketPool.getFreshPacket(); if(asm != null){ off = offset; asmbuf = asm.buf; asmbuf[0] = buf[0]; // ipv4, hdrlen, dsf, (wrong) total len asmbuf[1] = buf[1]; // id, flags asmbuf[2] = buf[2]; // ttl, prot, hdr chksum setHead(0); asmbuf[3] = buf[3]; // src ip asmbuf[4] = buf[4]; // dst ip asm.setLen(20); // reassembled packet wont have options in it setHole(0, 0xffff, 0); reassemble(buf); timestamp = (int)System.currentTimeMillis(); } } else{ if(relatedPacket(buf)){ // Dbg.wr("fragment related to the currently processed datagram... "); if(reassemble(buf)){ asmbuf[0] = 0x45000000|asm.len(); asm.setLinkLayer(p.linkLayer()); ret = asm; asm = null; // Dbg.wr("packet complete!\n"); // ret.print(0); } } // atm we support one concurrent reassembly only, // so we need to drop unrelated fragments. } } p.free(); return ret; } public static void setAsmTimeout(int asmTimeout){ Reassembler.asmTimeout = asmTimeout; } } }