/* * 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 jembench.ejip; /* * Changelog: */ /** * TCP functions. * * @author martin * * TODO: timer shall be an int with the correct subtraction calculation * see Timer.java * */ public class Tcp implements Runnable { /** * TCP protocol number */ public static final int PROTOCOL = 6; // TCP connection states public final static int FREE = -1; public final static int CLOSED = 0; public final static int LISTEN = 1; public final static int SYN_RCVD = 2; public final static int SYN_SENT = 3; public final static int ESTABLISHED = 4; public final static int CLOSE_WAIT = 5; public final static int LAST_ACK = 6; public final static int FIN_WAIT_1 = 7; public final static int FIN_WAIT_2 = 8; public final static int CLOSING = 9; public final static int TIME_WAIT = 10; // TCP flags static final int FL_URG = 0x20; static final int FL_ACK = 0x10; static final int FL_PSH = 0x8; static final int FL_RST = 0x4; static final int FL_SYN = 0x2; static final int FL_FIN = 0x1; /** * Offset of TCP header in words. */ public static final int HEAD = 5; /** * Offset of sequence number. */ public static final int SEQNR = 6; /** * Offset of acknowledgment number. */ public static final int ACKNR = 7; public static final int FLAGS = 8; public static final int CHKSUM = 9; public static final int OPTION = 10; /** * Offset of data in words when no options present */ public static final int DATA = 10; /** * 500 ms timer tick for the timeout handlers */ public static final int TIMER_TICK = 500; /** * 8 seconds until a connection is timed out for TIME_WAIT (very short!) */ public static final int TIME_WAIT_TIMEOUT = 8000/TIMER_TICK; /** * 60 seconds until a connection is timed out when being idle */ public static final int USER_TIMEOUT = 60000/TIMER_TICK; /** * Timout for retransmit in timer ticks. */ public static final int TIMEOUT = 2000/TIMER_TICK; /** * Maximum retransmissions. */ public static final int MAX_RETRANSMIT = 10; /** * The timer. */ private static int timer; private static Object mutex; public static final int MAX_HANDLER = 8; private TcpHandler[] list; private int[] ports; private int loopCnt; private int conLoopCnt; private Ejip ejip; public Tcp(Ejip ejipRef) { ejip = ejipRef; mutex = new Object(); list = new TcpHandler[MAX_HANDLER]; ports = new int[MAX_HANDLER]; loopCnt = 0; conLoopCnt = 0; // TODO: why is this a (char)? timer = (char)System.currentTimeMillis() + TIMER_TICK; } /** * add a handler for TCP requests. * returns false if list is full. */ public boolean addHandler(int port, TcpHandler h) { synchronized(mutex) { for (int i=0; i<MAX_HANDLER; ++i) { if (list[i]==null) { ports[i] = port; list[i] = h; return true; } } } return false; } /** * remove a handler for TCP requests. * returns false if it was not in the list. */ public boolean removeHandler(int port) { synchronized(mutex) { for (int i=0; i<MAX_HANDLER; ++i) { if (list[i]!=null) { if (ports[i] == port) { list[i] = null; return true; } } } } return false; } /** * Called periodically from Net for timeout processing. */ public void run() { int i; if ((short)(timer - System.currentTimeMillis()) < 0) { // this is probably a quite big synchronized block // for a SLIP connection synchronized (mutex) { TcpConnection tc = TcpConnection.connections[conLoopCnt]; // if (Logging.LOG) { // Logging.intVal(tc.idleTime); // Logging.wr("; "); // } tc.idleTime--; if (tc.state != FREE && tc.idleTime < 0) { if (Logging.LOG) { Logging.wr("Forced shutdown"); Logging.lf(); } tc.close(ejip); return; } if (tc.outStanding!=null) { tc.timeout--; if (tc.timeout==0) { // TODO: exponential backoff tc.retryCnt++; if (tc.retryCnt==MAX_RETRANSMIT) { Logging.wr("maximum retransmit - close"); tc.close(ejip); tc.retryCnt=0; } else { tc.timeout = TIMEOUT; // let it retransmit Logging.wr("retransmit"); tc.outStanding.interf.txQueue.enq(tc.outStanding); } } } } ++conLoopCnt; // All connection done, restart timer if (conLoopCnt==TcpConnection.CNT) { conLoopCnt = 0; timer = (short)(System.currentTimeMillis() + TIMER_TICK); } } else { // do an application poll i = loopCnt; synchronized (mutex) { if (list[i]!=null) { list[i].run(); ++i; if (i==MAX_HANDLER) i=0; } else { i = 0; } } loopCnt = i; } } /** * process packet and generate reply if necessary. */ void process(Packet p) { int[] buf = p.buf; // TODO add options on checksum buf[Ip.CHKSUM] = (PROTOCOL<<16) + p.len - 20; // set protocol and TCP length in iph checksum for tcp checksum if (Ip.chkSum(buf, 2, p.len-8)!=0) { ejip.returnPacket(p); // mark packet free return; } TcpConnection tc = TcpConnection.findConnection(p); // connection pool is empty, drop the packet if (tc==null) { ejip.returnPacket(p); return; } // We could add the handler to the connection to find // it faster TcpHandler th = null; // is a handler registered for that port? for (int i=0; i<MAX_HANDLER; ++i) { synchronized (mutex) { if (list[i]!=null && ports[i]==tc.localPort) { th = list[i]; break; } } } // no handler found if (th==null) { ejip.returnPacket(p); tc.close(ejip); if (Logging.LOG) Logging.lf(); if (Logging.LOG) Logging.wr('T'); if (Logging.LOG) Logging.intVal(buf[HEAD] & 0xffff); return; } // do the TCP state machine handleState(p, th, tc); } /** * Handle the TCP state machine * @param p incoming packet * @param tc connection * @return false means nothing more to do */ private void handleState(Packet p, TcpHandler th, TcpConnection tc) { // TODO: do we need synchronized for handle state? // or synchronized handling of connection change? // we only come into this when a handler is registered // so there is no use of state closed if (tc.state==CLOSED) { tc.state = LISTEN; } int state = tc.state; int buf[] = p.buf; int i = buf[8] >>> 16; int flags = i & 0xff; int hlen = i >>> 12; int datlen = p.len - 20 - (hlen << 2); Packet h; if (Logging.LOG) { Logging.wr("TCP state: "); Logging.intVal(state); // Logging.wr(" len "); // Logging.intVal(datlen); Logging.lf(); } if (!checkConnection(p, th, tc, flags, state)) { return; } // we received _something_ on this connection synchronized (mutex) { tc.idleTime = USER_TIMEOUT; } if (!checkAck(p, th, tc, flags)) { return; } switch(state) { case Tcp.CLOSED: // we should not receive a packet in state CLOSED // that means no one is listening // shall we send a RST? // we don't ever get here! if (Logging.LOG) { Logging.wr("shutdown from CLOSED "); } ejip.returnPacket(p); tc.close(ejip); return; case Tcp.LISTEN: // set the connection for the handler th.connection = tc; // TODO: read options (MSS) tc.rcvNxt = buf[SEQNR]+1; tc.sndNxt = (int)System.currentTimeMillis(); // TODO: get time dependent initial seqnrs buf[OPTION] = 0x02040000 + 512; // set MSS to 512 p.len = (OPTION+1)<<2; // len in bytes fillHeader(p, tc, FL_SYN|FL_ACK); tc.sndNxt++; // SYN send counts for one tc.state = SYN_RCVD; break; case Tcp.SYN_RCVD: if ((flags&FL_ACK) != 0 && buf[ACKNR]==tc.sndNxt) { if (Logging.LOG) { Logging.wr("SYN acked "); } tc.state = ESTABLISHED; h = th.established(p); if (h != null) { // only reply if necessary fillHeader(h, tc, FL_ACK); tc.sndNxt += p.len-(DATA<<2); } else { ejip.returnPacket(p); } } else { ejip.returnPacket(p); } break; case Tcp.SYN_SENT: if ((flags&FL_SYN) == 0) { ejip.returnPacket(p); if (Logging.LOG) { Logging.wr("dropped non SYN packet in SYN_SENT "); } return; } tc.rcvNxt = buf[SEQNR]+1; p.len = DATA<<2; // len in bytes tc.state = ESTABLISHED; h = th.established(p); if (h == null) { // return plain ack Ip.setData(p, Tcp.DATA, ""); fillHeader(p, tc, FL_ACK); tc.sndNxt += p.len-(DATA<<2); } else { fillHeader(h, tc, FL_ACK); tc.sndNxt += h.len-(DATA<<2); } break; case Tcp.ESTABLISHED: if ((flags&FL_FIN)!=0) { if (Logging.LOG) { Logging.wr("FIN received "); } // TODO check SEQNR tc.rcvNxt = buf[SEQNR]+1; p.len = DATA<<2; fillHeader(p, tc, FL_ACK|FL_FIN); tc.sndNxt++; // FIN send counts for one tc.state = LAST_ACK; break; } // again ignored any options int len = p.len-(DATA<<2); if (buf[SEQNR]==tc.rcvNxt) { if (Logging.LOG) { Logging.wr("do request "); } h = th.request(p); tc.rcvNxt += len; if (h==null) { if (flags==FL_ACK && len==0 && !th.finished()) { // nothing to send and nothing to ack } else { flags = FL_ACK | (th.finished() ? FL_FIN : 0); // return plain ack Ip.setData(p, Tcp.DATA, ""); fillHeader(p, tc, flags); tc.sndNxt += p.len-(DATA<<2); } } else { flags = FL_ACK | (th.finished() ? FL_FIN : 0); fillHeader(h, tc, flags); tc.sndNxt += h.len-(DATA<<2); } if (th.finished()) { if (Logging.LOG) { Logging.wr("send FIN "); } tc.sndNxt++; tc.state = FIN_WAIT_1; } } else { ejip.returnPacket(p); // TODO ack last segment if (Logging.LOG) { Logging.wr("dropped wrong SEQNR "); } } break; case Tcp.CLOSE_WAIT: // we do not need this state as we are not interested // in half open connections if (Logging.LOG) { Logging.wr("spurious state CLOSE_WAIT "); } ejip.returnPacket(p); break; case Tcp.LAST_ACK: if (Logging.LOG) { Logging.wr("shutdown from LAST_ACK "); } // reset the connection for the handler th.connection = null; ejip.returnPacket(p); tc.close(ejip); break; case Tcp.FIN_WAIT_1: // reset the connection for the handler th.connection = null; tc.rcvNxt = buf[SEQNR]+1; if ((flags&FL_FIN) != 0) { fillHeader(p, tc, FL_ACK); tc.sndNxt++; // FIN send counts for one if ((flags&FL_ACK) != 0) { if (Logging.LOG) { Logging.wr("wait from FIN_WAIT_1 "); } tc.state = TIME_WAIT; tc.idleTime = TIME_WAIT_TIMEOUT; } else { tc.state = CLOSING; } } else { ejip.returnPacket(p); tc.state = FIN_WAIT_2; } break; case Tcp.FIN_WAIT_2: tc.rcvNxt = buf[SEQNR]+1; if ((flags&FL_FIN) != 0) { fillHeader(p, tc, FL_ACK); tc.sndNxt++; // FIN send counts for one if (Logging.LOG) { Logging.wr("wait from FIN_WAIT_2 "); } tc.state = TIME_WAIT; tc.idleTime = TIME_WAIT_TIMEOUT; } else { ejip.returnPacket(p); } break; case Tcp.CLOSING: if (Logging.LOG) { Logging.wr("shutdown from CLOSING "); } // drop packet, but keep connection alive ejip.returnPacket(p); tc.state = TIME_WAIT; tc.idleTime = TIME_WAIT_TIMEOUT; break; case Tcp.TIME_WAIT: if (Logging.LOG) { Logging.wr("dropped packet in TIME_WAIT "); } // just discard packet ejip.returnPacket(p); break; } } /** * TODO: explain ... at least packet handling... * * Consumed the packet (either returned or sent) when returning false. * On true return the packet still needs to be handeled. * @param p * @param th * @param tc * @param flags * @param state * @return */ private boolean checkConnection(Packet p, TcpHandler th, TcpConnection tc, int flags, int state) { int buf[] = p.buf; // reset the connection synchronized (mutex) { if ((flags&FL_RST)!=0) { if (Logging.LOG) { Logging.wr("RST received"); Logging.lf(); } // reset the connection for the handler th.connection = null; ejip.returnPacket(p); tc.close(ejip); return false; } } // reset if what we received does not match our state synchronized (mutex) { if (state == LISTEN && (flags & FL_SYN) == 0) { //ejip.returnPacket(p); // reset the connection tc.rcvNxt = buf[SEQNR]+p.len-(DATA<<2); tc.sndNxt = buf[ACKNR]; Ip.setData(p, Tcp.DATA, ""); fillHeader(p, tc, FL_RST|FL_ACK); if (Logging.LOG) { Logging.wr("dropped non SYN packet in LISTEN"); Logging.lf(); } return false; } } // check if the handler is busy synchronized (mutex) { if (th.connection != null && th.connection != tc && th.connection.state != Tcp.FREE) { // System.out.print("wrong connection: "); // System.out.print(tc); // System.out.print(" got already "); // System.out.print(th.connection); // System.out.print(" state "); // System.out.println(th.connection.state); ejip.returnPacket(p); return false; } } return true; } /** * TODO: explain... * * Packet consumed on false return. * * @param p * @param th * @param tc * @param flags * @return */ private boolean checkAck(Packet p, TcpHandler th, TcpConnection tc, int flags) { int buf[] = p.buf; // check for ACK synchronized (mutex) { if ((flags&FL_ACK)!=0 && tc.outStanding!=null) { if (buf[ACKNR]==tc.sndNxt) { if (Logging.LOG) { Logging.wr("ACK received"); Logging.lf(); } Packet os = tc.outStanding; // recycle the outstanding packet and reset isTcpOnFly tc.outStanding = null; os.isTcpOnFly = false; ejip.returnPacket(os); } else { // not the correct ACK - drop it ejip.returnPacket(p); if (Logging.LOG) { Logging.wr("dropped wrong ACKNR"); Logging.lf(); } return false; } } } if (tc.outStanding!=null) { // we handle only one packet at a time // so we have to drop it. ejip.returnPacket(p); if (Logging.LOG) { Logging.wr("waiting on ACK - dropped"); Logging.lf(); } return false; } return true; } public void startConnection(LinkLayer ll, int ip, int port) { Packet p = ejip.getFreePacket(ll); p.buf[OPTION] = 0x02040000 + 512; // set MSS to 512 p.len = (OPTION+1)<<2; // len in bytes TcpConnection tc = TcpConnection.findConnection(ip, port, ll.getIpAddress(), 10000+port); tc.sndNxt = (int)System.currentTimeMillis(); // TODO: get time dependent initial seqnrs fillHeader(p, tc, FL_SYN); tc.sndNxt++; // SYN send counts for one tc.state = SYN_SENT; } /** * Fill in the header and enqueue the packet into the send queue. * * @param p * @param tc * @param fl */ void fillHeader(Packet p, TcpConnection tc, int fl) { // Do we really free it here? if (p.len==0) { ejip.returnPacket(p); // mark packet free return; } int buf[] = p.buf; // IP header (without checksum) buf[0] = 0x45000000 + p.len; // ip length (header without options) buf[1] = Ip.getId(); // identification, no fragmentation buf[Ip.SOURCE] = tc.localIP; buf[Ip.DESTINATION] = tc.remoteIP; buf[HEAD] = (tc.localPort << 16) + tc.remotePort; buf[SEQNR] = tc.sndNxt; buf[ACKNR] = (fl & FL_ACK) != 0 ? tc.rcvNxt : 0; // TODO: set window according to buffer if ((fl&FL_SYN)!=0) { buf[FLAGS] = 0x60000000 + (fl << 16) + 512; // hlen = 24, mss option } else { buf[FLAGS] = 0x50000000 + (fl << 16) + 512; // hlen = 20, no options } buf[CHKSUM] = 0; // clear checksum field buf[Ip.CHKSUM] = (PROTOCOL << 16) + p.len - 20; // set protocol and tcp length // in iph checksum for tcp // checksum buf[CHKSUM] = Ip.chkSum(buf, 2, p.len - 8) << 16; // TODO: set to 0xffff if 0, or is this only in UDP? // fill in IP header, swap IP addresses and mark for send // for TCP checksum used field of IP header buf[Ip.CHKSUM] = (0x20 << 24) + (PROTOCOL << 16); // ttl, protocol, clear checksum buf[Ip.CHKSUM] |= Ip.chkSum(buf, 0, 20); p.llh[6] = 0x0800; // packets with data or the SYN/FIN set // need to be retransmitted if (p.len>(DATA<<2) || (fl & (FL_SYN|FL_FIN))!=0) { synchronized (mutex) { tc.outStanding = p; tc.timeout = TIMEOUT; } p.isTcpOnFly = true; } // we send _something_ on this connection synchronized (mutex) { tc.idleTime = USER_TIMEOUT; } p.interf.txQueue.enq(p); } }